From c3f27ad0ca5609f7a3867ecea1dea682c4c0bfe9 Mon Sep 17 00:00:00 2001 From: Emily Miller Date: Thu, 30 Sep 2021 15:48:35 -0400 Subject: [PATCH 01/29] Finished front end challenge --- HFrontEndChallenge/local-server.js | 53 + HFrontEndChallenge/package-lock.json | 1786 ++++++++++++++++++ HFrontEndChallenge/package.json | 20 + HFrontEndChallenge/public/css/main.css | 51 + HFrontEndChallenge/public/img/emily-icon.png | Bin 0 -> 34341 bytes HFrontEndChallenge/public/js/script.js | 106 ++ HFrontEndChallenge/views/index.ejs | 85 + HFrontEndChallenge/views/verify.php | 16 + 8 files changed, 2117 insertions(+) create mode 100644 HFrontEndChallenge/local-server.js create mode 100644 HFrontEndChallenge/package-lock.json create mode 100644 HFrontEndChallenge/package.json create mode 100644 HFrontEndChallenge/public/css/main.css create mode 100644 HFrontEndChallenge/public/img/emily-icon.png create mode 100644 HFrontEndChallenge/public/js/script.js create mode 100644 HFrontEndChallenge/views/index.ejs create mode 100644 HFrontEndChallenge/views/verify.php diff --git a/HFrontEndChallenge/local-server.js b/HFrontEndChallenge/local-server.js new file mode 100644 index 000000000..9328ec26f --- /dev/null +++ b/HFrontEndChallenge/local-server.js @@ -0,0 +1,53 @@ +// Imports +const express = require('express') +const app = express() +const port = 5000 +var bodyParser = require('body-parser'); +var request = require('request'); + + +app.use(bodyParser.json()); +app.use(bodyParser.urlencoded({extended : false})); + +// Static Files +app.use(express.static('public')); +// Specific folder example +app.use('/css', express.static(__dirname + 'public/css')) +app.use('/js', express.static(__dirname + 'public/js')) +app.use('/img', express.static(__dirname + 'public/images')) + +app.post('/submit',function(req,res){ + // g-recaptcha-response is the key that browser will generate upon form submit. + // if its blank or null means user has not selected the captcha, so return the error. + if(req.body['g-recaptcha-response'] === undefined || req.body['g-recaptcha-response'] === '' || req.body['g-recaptcha-response'] === null) { + return res.json({"responseCode" : 1,"responseDesc" : "Please select captcha"}); + } + // Put your secret key here. + var secretKey = "6LfXDZ4cAAAAABJ7h33X-SPdQ1VMBuD4tUnen4KB"; + // req.connection.remoteAddress will provide IP address of connected user. + var verificationUrl = "https://www.google.com/recaptcha/api/siteverify?secret=" + secretKey + "&response=" + req.body['g-recaptcha-response']; + // Hitting GET request to the URL, Google will respond with success or error scenario. + request(verificationUrl,function(error,response,body) { + body = JSON.parse(body); + // Success will be true or false depending upon captcha validation. + if(body.success !== undefined && !body.success) { + return res.json({"responseCode" : 1,"responseDesc" : "Failed captcha verification"}); + } + res.json(body); + }); + }); + +// Set View's +app.set('views', './views'); +app.set('view engine', 'ejs'); + +// Navigation +app.get('', (req, res) => { + res.render('index', { text: 'Hey' }) +}) + +app.get('/about', (req, res) => { + res.sendFile(__dirname + '/views/about.html') +}) + +app.listen(port, () => console.info(`App listening on port ${port}`)) \ No newline at end of file diff --git a/HFrontEndChallenge/package-lock.json b/HFrontEndChallenge/package-lock.json new file mode 100644 index 000000000..1c0d03285 --- /dev/null +++ b/HFrontEndChallenge/package-lock.json @@ -0,0 +1,1786 @@ +{ + "name": "hfrontendchallenge", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@sindresorhus/is": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", + "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", + "dev": true + }, + "@szmarczak/http-timer": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", + "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", + "dev": true, + "requires": { + "defer-to-connect": "^1.0.1" + } + }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + } + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "dev": true, + "requires": { + "string-width": "^4.1.0" + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "async": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", + "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", + "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true + }, + "body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "requires": { + "bytes": "3.1.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + } + }, + "boxen": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", + "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==", + "dev": true, + "requires": { + "ansi-align": "^3.0.0", + "camelcase": "^6.2.0", + "chalk": "^4.1.0", + "cli-boxes": "^2.2.1", + "string-width": "^4.2.2", + "type-fest": "^0.20.2", + "widest-line": "^3.1.0", + "wrap-ansi": "^7.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + }, + "cacheable-request": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", + "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", + "dev": true, + "requires": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^3.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^4.1.0", + "responselike": "^1.0.2" + }, + "dependencies": { + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true + } + } + }, + "camelcase": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "dev": true + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "chokidar": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", + "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + } + }, + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, + "cli-boxes": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", + "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", + "dev": true + }, + "clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "dev": true, + "requires": { + "mimic-response": "^1.0.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "configstore": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", + "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", + "dev": true, + "requires": { + "dot-prop": "^5.2.0", + "graceful-fs": "^4.1.2", + "make-dir": "^3.0.0", + "unique-string": "^2.0.0", + "write-file-atomic": "^3.0.0", + "xdg-basedir": "^4.0.0" + } + }, + "content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "requires": { + "safe-buffer": "5.1.2" + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "dev": true + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", + "dev": true, + "requires": { + "mimic-response": "^1.0.0" + } + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true + }, + "defer-to-connect": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", + "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==", + "dev": true + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "dev": true, + "requires": { + "is-obj": "^2.0.0" + } + }, + "duplexer3": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", + "dev": true + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "ejs": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.6.tgz", + "integrity": "sha512-9lt9Zse4hPucPkoP7FHDF0LQAlGyF9JVpnClFLFH3aSSbxmyoqINRpp/9wePWJTUl4KOQwRL72Iw3InHPDkoGw==", + "requires": { + "jake": "^10.6.1" + } + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "escape-goat": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", + "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==", + "dev": true + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, + "express": { + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "requires": { + "accepts": "~1.3.7", + "array-flatten": "1.1.1", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", + "content-type": "~1.0.4", + "cookie": "0.4.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.1.2", + "send": "0.17.1", + "serve-static": "1.14.1", + "setprototypeof": "1.1.1", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "filelist": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.2.tgz", + "integrity": "sha512-z7O0IS8Plc39rTCq6i6iHxk43duYOn8uFJiWSewIq0Bww1RNybVHSCjahmcC87ZqAm4OTvFzlzeGu3XAzG1ctQ==", + "requires": { + "minimatch": "^3.0.4" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + } + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "global-dirs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.0.tgz", + "integrity": "sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==", + "dev": true, + "requires": { + "ini": "2.0.0" + } + }, + "got": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", + "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", + "dev": true, + "requires": { + "@sindresorhus/is": "^0.14.0", + "@szmarczak/http-timer": "^1.1.2", + "cacheable-request": "^6.0.0", + "decompress-response": "^3.3.0", + "duplexer3": "^0.1.4", + "get-stream": "^4.1.0", + "lowercase-keys": "^1.0.1", + "mimic-response": "^1.0.1", + "p-cancelable": "^1.0.0", + "to-readable-stream": "^1.0.0", + "url-parse-lax": "^3.0.0" + } + }, + "graceful-fs": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", + "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", + "dev": true + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "requires": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "has-yarn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", + "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", + "dev": true + }, + "http-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", + "dev": true + }, + "http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=", + "dev": true + }, + "import-lazy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", + "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "ini": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", + "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", + "dev": true + }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "dev": true, + "requires": { + "ci-info": "^2.0.0" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-installed-globally": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", + "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", + "dev": true, + "requires": { + "global-dirs": "^3.0.0", + "is-path-inside": "^3.0.2" + } + }, + "is-npm": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-5.0.0.tgz", + "integrity": "sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true + }, + "is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "is-yarn-global": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", + "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "jake": { + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.2.tgz", + "integrity": "sha512-eLpKyrfG3mzvGE2Du8VoPbeSkRry093+tyNjdYaBbJS9v17knImYGNXQCUV0gLxQtF82m3E8iRb/wdSQZLoq7A==", + "requires": { + "async": "0.9.x", + "chalk": "^2.4.2", + "filelist": "^1.0.1", + "minimatch": "^3.0.4" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, + "json-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", + "dev": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "keyv": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", + "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", + "dev": true, + "requires": { + "json-buffer": "3.0.0" + } + }, + "latest-version": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", + "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", + "dev": true, + "requires": { + "package-json": "^6.3.0" + } + }, + "lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "dev": true + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "requires": { + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "mime-db": { + "version": "1.49.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.49.0.tgz", + "integrity": "sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA==" + }, + "mime-types": { + "version": "2.1.32", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.32.tgz", + "integrity": "sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A==", + "requires": { + "mime-db": "1.49.0" + } + }, + "mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" + }, + "nodemon": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.13.tgz", + "integrity": "sha512-UMXMpsZsv1UXUttCn6gv8eQPhn6DR4BW+txnL3IN5IHqrCwcrT/yWHfL35UsClGXknTH79r5xbu+6J1zNHuSyA==", + "dev": true, + "requires": { + "chokidar": "^3.2.2", + "debug": "^3.2.6", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.0.4", + "pstree.remy": "^1.1.7", + "semver": "^5.7.1", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.3", + "update-notifier": "^5.1.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + } + } + }, + "nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", + "dev": true, + "requires": { + "abbrev": "1" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "normalize-url": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", + "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==", + "dev": true + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "p-cancelable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", + "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", + "dev": true + }, + "package-json": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", + "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", + "dev": true, + "requires": { + "got": "^9.6.0", + "registry-auth-token": "^4.0.0", + "registry-url": "^5.0.0", + "semver": "^6.2.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "picomatch": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "dev": true + }, + "prepend-http": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", + "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", + "dev": true + }, + "proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "requires": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + } + }, + "psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" + }, + "pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "pupa": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz", + "integrity": "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==", + "dev": true, + "requires": { + "escape-goat": "^2.0.0" + } + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, + "raw-body": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "requires": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + } + } + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "registry-auth-token": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz", + "integrity": "sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw==", + "dev": true, + "requires": { + "rc": "^1.2.8" + } + }, + "registry-url": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", + "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", + "dev": true, + "requires": { + "rc": "^1.2.8" + } + }, + "request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "dependencies": { + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + } + } + }, + "responselike": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", + "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", + "dev": true, + "requires": { + "lowercase-keys": "^1.0.0" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "semver-diff": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", + "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", + "dev": true, + "requires": { + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "dependencies": { + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } + }, + "serve-static": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.1" + } + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + }, + "signal-exit": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.5.tgz", + "integrity": "sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ==", + "dev": true + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + }, + "to-readable-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", + "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + }, + "touch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", + "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", + "dev": true, + "requires": { + "nopt": "~1.0.10" + } + }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "requires": { + "is-typedarray": "^1.0.0" + } + }, + "undefsafe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.3.tgz", + "integrity": "sha512-nrXZwwXrD/T/JXeygJqdCO6NZZ1L66HrxM/Z7mIq2oPanoN0F1nLx3lwJMu6AwJY69hdixaFQOuoYsMjE5/C2A==", + "dev": true, + "requires": { + "debug": "^2.2.0" + } + }, + "unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "dev": true, + "requires": { + "crypto-random-string": "^2.0.0" + } + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "update-notifier": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-5.1.0.tgz", + "integrity": "sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw==", + "dev": true, + "requires": { + "boxen": "^5.0.0", + "chalk": "^4.1.0", + "configstore": "^5.0.1", + "has-yarn": "^2.1.0", + "import-lazy": "^2.1.0", + "is-ci": "^2.0.0", + "is-installed-globally": "^0.4.0", + "is-npm": "^5.0.0", + "is-yarn-global": "^0.3.0", + "latest-version": "^5.1.0", + "pupa": "^2.1.1", + "semver": "^7.3.4", + "semver-diff": "^3.1.1", + "xdg-basedir": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "requires": { + "punycode": "^2.1.0" + } + }, + "url-parse-lax": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", + "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", + "dev": true, + "requires": { + "prepend-http": "^2.0.0" + } + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "widest-line": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", + "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", + "dev": true, + "requires": { + "string-width": "^4.0.0" + } + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "xdg-basedir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", + "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } +} diff --git a/HFrontEndChallenge/package.json b/HFrontEndChallenge/package.json new file mode 100644 index 000000000..fc6936b75 --- /dev/null +++ b/HFrontEndChallenge/package.json @@ -0,0 +1,20 @@ +{ + "name": "hfrontendchallenge", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "dependencies": { + "body-parser": "^1.19.0", + "ejs": "^3.1.6", + "express": "^4.17.1", + "request": "^2.88.2" + }, + "devDependencies": { + "nodemon": "^2.0.13" + } +} diff --git a/HFrontEndChallenge/public/css/main.css b/HFrontEndChallenge/public/css/main.css new file mode 100644 index 000000000..5d3188476 --- /dev/null +++ b/HFrontEndChallenge/public/css/main.css @@ -0,0 +1,51 @@ +.main-icon { + text-align: center; + } + +body {background-color: #404040;} + +h1 { + text-align: center; + display: block; + font-size: 2em; + font-family: Sans-serif; + margin-top: 100px; + margin-bottom: 0.67em; + margin-left: 0; + margin-right: 0; + font-weight: bold; + color: white; + } + +p { + text-align: center; + display: block; + font-size: 2em; + margin-top: 0.67em; + margin-bottom: 0.67em; + margin-left: 0; + margin-right: 0; + font-family: Sans-serif; + color: white; + } + +.leftcenter { +margin: 0; +position: absolute; +left: 50%; +-ms-transform: translate(-50%, -50%); +transform: translate(-50%, -50%); +} + +.center { +margin: 0; +position: absolute; +left: 50%; +top: 50%; +-ms-transform: translate(-50%, -50%); +transform: translate(-50%, -50%); +} + +.contact-button { + margin-top: 40px; + } diff --git a/HFrontEndChallenge/public/img/emily-icon.png b/HFrontEndChallenge/public/img/emily-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..905cfd9acd876894ff9fcea7cfed9a01dedbf8a0 GIT binary patch literal 34341 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�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 X96E_xGyVYz@FORs^r1|`B;fx8dQi{Q literal 0 HcmV?d00001 diff --git a/HFrontEndChallenge/public/js/script.js b/HFrontEndChallenge/public/js/script.js new file mode 100644 index 000000000..a5e8c41ce --- /dev/null +++ b/HFrontEndChallenge/public/js/script.js @@ -0,0 +1,106 @@ +function onSubmit(token) { + console.log("submitted called"); + $('#comment_form').submit(); + console.log("Submitted" + token); + //Very important line, it disable the page refresh. + return false; +} +$(document).ready(function() { + console.log("Ready"); + $('#comment_form').submit(function() { + $(this).ajaxSubmit({ + error: function(xhr) { + console.log("Error"); + }, + success: function(response) { + try { + var parsed_data = JSON.parse(response.responseCode); + if (parsed_data==1) + { + console.log("Recaptcha failed, alert user & return"); + alert("Recaptcha failed, try again :(") + return; + } + } catch (error) { + console.log("responseCode is undefined, must have passed"); + } + //if it made it to here, it passed the recaptcha check so display info + var name = document.getElementById("name"); + var email = document.getElementById("email"); + var phoneNumber = document.getElementById("phoneNumber"); + var questions = document.getElementById("questions"); + console.log("Form submission: ------------------"); + console.log("Name: " + name.value); + console.log("Email: " + email.value); + console.log("Phone Number: " + phoneNumber.value); + console.log("Questions: " + questions.value); + console.log("End form submission: ---------------"); + console.log("The recaptcha worked, the response is below: "); + console.log(response); + isSubmitted=true; + //switch form to close form + toggleOnSubmit(); + } + }); + //disables refresh + return false; + }); +}); + +var isSubmitted = false; +console.log("Being read"); +function sConsole(event) { + event.preventDefault(); + var name = document.getElementById("name"); + var email = document.getElementById("email"); + var phoneNumber = document.getElementById("phoneNumber"); + var questions = document.getElementById("questions"); + console.log("Form submission: ------------------"); + console.log("Name: " + name.value); + console.log("Email: " + email.value); + console.log("Phone Number: " + phoneNumber.value); + console.log("Questions: " + questions.value); + console.log("End form submission: ---------------"); + if (name.value!="") { + document.getElementById("form").innerHTML="Thank you, " + name.value; + document.getElementById("form").reset(); + } + else + document.getElementById("form").innerHTML="Thank you!"; + + isSubmitted=true; + toggleOnSubmit(); +} + +function toggleOnSubmit() { + if (isSubmitted==false) { + document.getElementById("comment_form").innerHTML= + `
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + `; + isSubmitted=true; + } + + else { + document.getElementById("comment_form").innerHTML= + `Thank you! We will be in touch soon.

`; + isSubmitted=false; + } + +} \ No newline at end of file diff --git a/HFrontEndChallenge/views/index.ejs b/HFrontEndChallenge/views/index.ejs new file mode 100644 index 000000000..f4d647eb1 --- /dev/null +++ b/HFrontEndChallenge/views/index.ejs @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + Emily's Idea Co. + + +

Emily's Idea Company

+
+ Lightbulb icon +
+ + + + + + + + + + +
+ The debugger caught an exception in your WSGI application. You can now + look at the traceback which led to the error. + If you enable JavaScript you can also use additional features such as code + execution (if the evalex feature is enabled), automatic pasting of the + exceptions and much more. +
+""" + + FOOTER + + """ + +""" +) + +CONSOLE_HTML = ( + HEADER + + """\ +

Interactive Console

+
+In this console you can execute Python expressions in the context of the +application. The initial namespace was created by the debugger automatically. +
+
The Console requires JavaScript.
+""" + + FOOTER +) + +SUMMARY_HTML = """\ +
+ %(title)s +
    %(frames)s
+ %(description)s +
+""" + +FRAME_HTML = """\ +
+

File "%(filename)s", + line %(lineno)s, + in %(function_name)s

+
%(lines)s
+
+""" + +SOURCE_LINE_HTML = """\ + + %(lineno)s + %(code)s + +""" + + +def render_console_html(secret: str, evalex_trusted: bool = True) -> str: + return CONSOLE_HTML % { + "evalex": "true", + "evalex_trusted": "true" if evalex_trusted else "false", + "console": "true", + "title": "Console", + "secret": secret, + "traceback_id": -1, + } + + +def get_current_traceback( + ignore_system_exceptions: bool = False, + show_hidden_frames: bool = False, + skip: int = 0, +) -> "Traceback": + """Get the current exception info as `Traceback` object. Per default + calling this method will reraise system exceptions such as generator exit, + system exit or others. This behavior can be disabled by passing `False` + to the function as first parameter. + """ + info = t.cast( + t.Tuple[t.Type[BaseException], BaseException, TracebackType], sys.exc_info() + ) + exc_type, exc_value, tb = info + + if ignore_system_exceptions and exc_type in { + SystemExit, + KeyboardInterrupt, + GeneratorExit, + }: + raise + for _ in range(skip): + if tb.tb_next is None: + break + tb = tb.tb_next + tb = Traceback(exc_type, exc_value, tb) + if not show_hidden_frames: + tb.filter_hidden_frames() + return tb + + +class Line: + """Helper for the source renderer.""" + + __slots__ = ("lineno", "code", "in_frame", "current") + + def __init__(self, lineno: int, code: str) -> None: + self.lineno = lineno + self.code = code + self.in_frame = False + self.current = False + + @property + def classes(self) -> t.List[str]: + rv = ["line"] + if self.in_frame: + rv.append("in-frame") + if self.current: + rv.append("current") + return rv + + def render(self) -> str: + return SOURCE_LINE_HTML % { + "classes": " ".join(self.classes), + "lineno": self.lineno, + "code": escape(self.code), + } + + +class Traceback: + """Wraps a traceback.""" + + def __init__( + self, + exc_type: t.Type[BaseException], + exc_value: BaseException, + tb: TracebackType, + ) -> None: + self.exc_type = exc_type + self.exc_value = exc_value + self.tb = tb + + exception_type = exc_type.__name__ + if exc_type.__module__ not in {"builtins", "__builtin__", "exceptions"}: + exception_type = f"{exc_type.__module__}.{exception_type}" + self.exception_type = exception_type + + self.groups = [] + memo = set() + while True: + self.groups.append(Group(exc_type, exc_value, tb)) + memo.add(id(exc_value)) + exc_value = exc_value.__cause__ or exc_value.__context__ # type: ignore + if exc_value is None or id(exc_value) in memo: + break + exc_type = type(exc_value) + tb = exc_value.__traceback__ # type: ignore + self.groups.reverse() + self.frames = [frame for group in self.groups for frame in group.frames] + + def filter_hidden_frames(self) -> None: + """Remove the frames according to the paste spec.""" + for group in self.groups: + group.filter_hidden_frames() + + self.frames[:] = [frame for group in self.groups for frame in group.frames] + + @property + def is_syntax_error(self) -> bool: + """Is it a syntax error?""" + return isinstance(self.exc_value, SyntaxError) + + @property + def exception(self) -> str: + """String representation of the final exception.""" + return self.groups[-1].exception + + def log(self, logfile: t.Optional[t.TextIO] = None) -> None: + """Log the ASCII traceback into a file object.""" + if logfile is None: + logfile = sys.stderr + tb = f"{self.plaintext.rstrip()}\n" + logfile.write(tb) + + def render_summary(self, include_title: bool = True) -> str: + """Render the traceback for the interactive console.""" + title = "" + classes = ["traceback"] + if not self.frames: + classes.append("noframe-traceback") + frames = [] + else: + library_frames = sum(frame.is_library for frame in self.frames) + mark_lib = 0 < library_frames < len(self.frames) + frames = [group.render(mark_lib=mark_lib) for group in self.groups] + + if include_title: + if self.is_syntax_error: + title = "Syntax Error" + else: + title = "Traceback (most recent call last):" + + if self.is_syntax_error: + description = f"
{escape(self.exception)}
" + else: + description = f"
{escape(self.exception)}
" + + return SUMMARY_HTML % { + "classes": " ".join(classes), + "title": f"

{title if title else ''}

", + "frames": "\n".join(frames), + "description": description, + } + + def render_full( + self, + evalex: bool = False, + secret: t.Optional[str] = None, + evalex_trusted: bool = True, + ) -> str: + """Render the Full HTML page with the traceback info.""" + exc = escape(self.exception) + return PAGE_HTML % { + "evalex": "true" if evalex else "false", + "evalex_trusted": "true" if evalex_trusted else "false", + "console": "false", + "title": exc, + "exception": exc, + "exception_type": escape(self.exception_type), + "summary": self.render_summary(include_title=False), + "plaintext": escape(self.plaintext), + "plaintext_cs": re.sub("-{2,}", "-", self.plaintext), + "traceback_id": self.id, + "secret": secret, + } + + @cached_property + def plaintext(self) -> str: + return "\n".join([group.render_text() for group in self.groups]) + + @property + def id(self) -> int: + return id(self) + + +class Group: + """A group of frames for an exception in a traceback. If the + exception has a ``__cause__`` or ``__context__``, there are multiple + exception groups. + """ + + def __init__( + self, + exc_type: t.Type[BaseException], + exc_value: BaseException, + tb: TracebackType, + ) -> None: + self.exc_type = exc_type + self.exc_value = exc_value + self.info = None + if exc_value.__cause__ is not None: + self.info = ( + "The above exception was the direct cause of the following exception" + ) + elif exc_value.__context__ is not None: + self.info = ( + "During handling of the above exception, another exception occurred" + ) + + self.frames = [] + while tb is not None: + self.frames.append(Frame(exc_type, exc_value, tb)) + tb = tb.tb_next # type: ignore + + def filter_hidden_frames(self) -> None: + new_frames: t.List[Frame] = [] + hidden = False + + for frame in self.frames: + hide = frame.hide + if hide in ("before", "before_and_this"): + new_frames = [] + hidden = False + if hide == "before_and_this": + continue + elif hide in ("reset", "reset_and_this"): + hidden = False + if hide == "reset_and_this": + continue + elif hide in ("after", "after_and_this"): + hidden = True + if hide == "after_and_this": + continue + elif hide or hidden: + continue + new_frames.append(frame) + + # if we only have one frame and that frame is from the codeop + # module, remove it. + if len(new_frames) == 1 and self.frames[0].module == "codeop": + del self.frames[:] + + # if the last frame is missing something went terrible wrong :( + elif self.frames[-1] in new_frames: + self.frames[:] = new_frames + + @property + def exception(self) -> str: + """String representation of the exception.""" + buf = traceback.format_exception_only(self.exc_type, self.exc_value) + rv = "".join(buf).strip() + return _to_str(rv, "utf-8", "replace") + + def render(self, mark_lib: bool = True) -> str: + out = [] + if self.info is not None: + out.append(f'
  • {self.info}:
    ') + for frame in self.frames: + title = f' title="{escape(frame.info)}"' if frame.info else "" + out.append(f"{frame.render(mark_lib=mark_lib)}") + return "\n".join(out) + + def render_text(self) -> str: + out = [] + if self.info is not None: + out.append(f"\n{self.info}:\n") + out.append("Traceback (most recent call last):") + for frame in self.frames: + out.append(frame.render_text()) + out.append(self.exception) + return "\n".join(out) + + +class Frame: + """A single frame in a traceback.""" + + def __init__( + self, + exc_type: t.Type[BaseException], + exc_value: BaseException, + tb: TracebackType, + ) -> None: + self.lineno = tb.tb_lineno + self.function_name = tb.tb_frame.f_code.co_name + self.locals = tb.tb_frame.f_locals + self.globals = tb.tb_frame.f_globals + + fn = inspect.getsourcefile(tb) or inspect.getfile(tb) + if fn[-4:] in (".pyo", ".pyc"): + fn = fn[:-1] + # if it's a file on the file system resolve the real filename. + if os.path.isfile(fn): + fn = os.path.realpath(fn) + self.filename = _to_str(fn, get_filesystem_encoding()) + self.module = self.globals.get("__name__", self.locals.get("__name__")) + self.loader = self.globals.get("__loader__", self.locals.get("__loader__")) + self.code = tb.tb_frame.f_code + + # support for paste's traceback extensions + self.hide = self.locals.get("__traceback_hide__", False) + info = self.locals.get("__traceback_info__") + if info is not None: + info = _to_str(info, "utf-8", "replace") + self.info = info + + def render(self, mark_lib: bool = True) -> str: + """Render a single frame in a traceback.""" + return FRAME_HTML % { + "id": self.id, + "filename": escape(self.filename), + "lineno": self.lineno, + "function_name": escape(self.function_name), + "lines": self.render_line_context(), + "library": "library" if mark_lib and self.is_library else "", + } + + @cached_property + def is_library(self) -> bool: + return any( + self.filename.startswith(os.path.realpath(path)) + for path in sysconfig.get_paths().values() + ) + + def render_text(self) -> str: + return ( + f' File "{self.filename}", line {self.lineno}, in {self.function_name}\n' + f" {self.current_line.strip()}" + ) + + def render_line_context(self) -> str: + before, current, after = self.get_context_lines() + rv = [] + + def render_line(line: str, cls: str) -> None: + line = line.expandtabs().rstrip() + stripped_line = line.strip() + prefix = len(line) - len(stripped_line) + rv.append( + f'
    {" " * prefix}'
    +                f"{escape(stripped_line) if stripped_line else ' '}
    " + ) + + for line in before: + render_line(line, "before") + render_line(current, "current") + for line in after: + render_line(line, "after") + + return "\n".join(rv) + + def get_annotated_lines(self) -> t.List[Line]: + """Helper function that returns lines with extra information.""" + lines = [Line(idx + 1, x) for idx, x in enumerate(self.sourcelines)] + + # find function definition and mark lines + if hasattr(self.code, "co_firstlineno"): + lineno = self.code.co_firstlineno - 1 + while lineno > 0: + if _funcdef_re.match(lines[lineno].code): + break + lineno -= 1 + try: + offset = len(inspect.getblock([f"{x.code}\n" for x in lines[lineno:]])) + except TokenError: + offset = 0 + for line in lines[lineno : lineno + offset]: + line.in_frame = True + + # mark current line + try: + lines[self.lineno - 1].current = True + except IndexError: + pass + + return lines + + def eval(self, code: t.Union[str, CodeType], mode: str = "single") -> t.Any: + """Evaluate code in the context of the frame.""" + if isinstance(code, str): + code = compile(code, "", mode) + return eval(code, self.globals, self.locals) + + @cached_property + def sourcelines(self) -> t.List[str]: + """The sourcecode of the file as list of strings.""" + # get sourcecode from loader or file + source = None + if self.loader is not None: + try: + if hasattr(self.loader, "get_source"): + source = self.loader.get_source(self.module) + elif hasattr(self.loader, "get_source_by_code"): + source = self.loader.get_source_by_code(self.code) + except Exception: + # we munch the exception so that we don't cause troubles + # if the loader is broken. + pass + + if source is None: + try: + with open(self.filename, mode="rb") as f: + source = f.read() + except OSError: + return [] + + # already str? return right away + if isinstance(source, str): + return source.splitlines() + + charset = "utf-8" + if source.startswith(codecs.BOM_UTF8): + source = source[3:] + else: + for idx, match in enumerate(_line_re.finditer(source)): + coding_match = _coding_re.search(match.group()) + if coding_match is not None: + charset = coding_match.group(1).decode("utf-8") + break + if idx > 1: + break + + # on broken cookies we fall back to utf-8 too + charset = _to_str(charset) + try: + codecs.lookup(charset) + except LookupError: + charset = "utf-8" + + return source.decode(charset, "replace").splitlines() + + def get_context_lines( + self, context: int = 5 + ) -> t.Tuple[t.List[str], str, t.List[str]]: + before = self.sourcelines[self.lineno - context - 1 : self.lineno - 1] + past = self.sourcelines[self.lineno : self.lineno + context] + return (before, self.current_line, past) + + @property + def current_line(self) -> str: + try: + return self.sourcelines[self.lineno - 1] + except IndexError: + return "" + + @cached_property + def console(self) -> Console: + return Console(self.globals, self.locals) + + @property + def id(self) -> int: + return id(self) diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/exceptions.py b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/exceptions.py new file mode 100644 index 000000000..16c3964d2 --- /dev/null +++ b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/exceptions.py @@ -0,0 +1,943 @@ +"""Implements a number of Python exceptions which can be raised from within +a view to trigger a standard HTTP non-200 response. + +Usage Example +------------- + +.. code-block:: python + + from werkzeug.wrappers.request import Request + from werkzeug.exceptions import HTTPException, NotFound + + def view(request): + raise NotFound() + + @Request.application + def application(request): + try: + return view(request) + except HTTPException as e: + return e + +As you can see from this example those exceptions are callable WSGI +applications. However, they are not Werkzeug response objects. You +can get a response object by calling ``get_response()`` on a HTTP +exception. + +Keep in mind that you may have to pass an environ (WSGI) or scope +(ASGI) to ``get_response()`` because some errors fetch additional +information relating to the request. + +If you want to hook in a different exception page to say, a 404 status +code, you can add a second except for a specific subclass of an error: + +.. code-block:: python + + @Request.application + def application(request): + try: + return view(request) + except NotFound as e: + return not_found(request) + except HTTPException as e: + return e + +""" +import sys +import typing as t +import warnings +from datetime import datetime +from html import escape + +from ._internal import _get_environ + +if t.TYPE_CHECKING: + import typing_extensions as te + from _typeshed.wsgi import StartResponse + from _typeshed.wsgi import WSGIEnvironment + from .datastructures import WWWAuthenticate + from .sansio.response import Response + from .wrappers.response import Response as WSGIResponse # noqa: F401 + + +class HTTPException(Exception): + """The base class for all HTTP exceptions. This exception can be called as a WSGI + application to render a default error page or you can catch the subclasses + of it independently and render nicer error messages. + """ + + code: t.Optional[int] = None + description: t.Optional[str] = None + + def __init__( + self, + description: t.Optional[str] = None, + response: t.Optional["Response"] = None, + ) -> None: + super().__init__() + if description is not None: + self.description = description + self.response = response + + @classmethod + def wrap( + cls, exception: t.Type[BaseException], name: t.Optional[str] = None + ) -> t.Type["HTTPException"]: + """Create an exception that is a subclass of the calling HTTP + exception and the ``exception`` argument. + + The first argument to the class will be passed to the + wrapped ``exception``, the rest to the HTTP exception. If + ``e.args`` is not empty and ``e.show_exception`` is ``True``, + the wrapped exception message is added to the HTTP error + description. + + .. deprecated:: 2.0 + Will be removed in Werkzeug 2.1. Create a subclass manually + instead. + + .. versionchanged:: 0.15.5 + The ``show_exception`` attribute controls whether the + description includes the wrapped exception message. + + .. versionchanged:: 0.15.0 + The description includes the wrapped exception message. + """ + warnings.warn( + "'HTTPException.wrap' is deprecated and will be removed in" + " Werkzeug 2.1. Create a subclass manually instead.", + DeprecationWarning, + stacklevel=2, + ) + + class newcls(cls, exception): # type: ignore + _description = cls.description + show_exception = False + + def __init__( + self, arg: t.Optional[t.Any] = None, *args: t.Any, **kwargs: t.Any + ) -> None: + super().__init__(*args, **kwargs) + + if arg is None: + exception.__init__(self) + else: + exception.__init__(self, arg) + + @property + def description(self) -> str: + if self.show_exception: + return ( + f"{self._description}\n" + f"{exception.__name__}: {exception.__str__(self)}" + ) + + return self._description # type: ignore + + @description.setter + def description(self, value: str) -> None: + self._description = value + + newcls.__module__ = sys._getframe(1).f_globals["__name__"] + name = name or cls.__name__ + exception.__name__ + newcls.__name__ = newcls.__qualname__ = name + return newcls + + @property + def name(self) -> str: + """The status name.""" + from .http import HTTP_STATUS_CODES + + return HTTP_STATUS_CODES.get(self.code, "Unknown Error") # type: ignore + + def get_description( + self, + environ: t.Optional["WSGIEnvironment"] = None, + scope: t.Optional[dict] = None, + ) -> str: + """Get the description.""" + if self.description is None: + description = "" + elif not isinstance(self.description, str): + description = str(self.description) + else: + description = self.description + + description = escape(description).replace("\n", "
    ") + return f"

    {description}

    " + + def get_body( + self, + environ: t.Optional["WSGIEnvironment"] = None, + scope: t.Optional[dict] = None, + ) -> str: + """Get the HTML body.""" + return ( + '\n' + f"{self.code} {escape(self.name)}\n" + f"

    {escape(self.name)}

    \n" + f"{self.get_description(environ)}\n" + ) + + def get_headers( + self, + environ: t.Optional["WSGIEnvironment"] = None, + scope: t.Optional[dict] = None, + ) -> t.List[t.Tuple[str, str]]: + """Get a list of headers.""" + return [("Content-Type", "text/html; charset=utf-8")] + + def get_response( + self, + environ: t.Optional["WSGIEnvironment"] = None, + scope: t.Optional[dict] = None, + ) -> "Response": + """Get a response object. If one was passed to the exception + it's returned directly. + + :param environ: the optional environ for the request. This + can be used to modify the response depending + on how the request looked like. + :return: a :class:`Response` object or a subclass thereof. + """ + from .wrappers.response import Response as WSGIResponse # noqa: F811 + + if self.response is not None: + return self.response + if environ is not None: + environ = _get_environ(environ) + headers = self.get_headers(environ, scope) + return WSGIResponse(self.get_body(environ, scope), self.code, headers) + + def __call__( + self, environ: "WSGIEnvironment", start_response: "StartResponse" + ) -> t.Iterable[bytes]: + """Call the exception as WSGI application. + + :param environ: the WSGI environment. + :param start_response: the response callable provided by the WSGI + server. + """ + response = t.cast("WSGIResponse", self.get_response(environ)) + return response(environ, start_response) + + def __str__(self) -> str: + code = self.code if self.code is not None else "???" + return f"{code} {self.name}: {self.description}" + + def __repr__(self) -> str: + code = self.code if self.code is not None else "???" + return f"<{type(self).__name__} '{code}: {self.name}'>" + + +class BadRequest(HTTPException): + """*400* `Bad Request` + + Raise if the browser sends something to the application the application + or server cannot handle. + """ + + code = 400 + description = ( + "The browser (or proxy) sent a request that this server could " + "not understand." + ) + + +class BadRequestKeyError(BadRequest, KeyError): + """An exception that is used to signal both a :exc:`KeyError` and a + :exc:`BadRequest`. Used by many of the datastructures. + """ + + _description = BadRequest.description + #: Show the KeyError along with the HTTP error message in the + #: response. This should be disabled in production, but can be + #: useful in a debug mode. + show_exception = False + + def __init__(self, arg: t.Optional[str] = None, *args: t.Any, **kwargs: t.Any): + super().__init__(*args, **kwargs) + + if arg is None: + KeyError.__init__(self) + else: + KeyError.__init__(self, arg) + + @property # type: ignore + def description(self) -> str: # type: ignore + if self.show_exception: + return ( + f"{self._description}\n" + f"{KeyError.__name__}: {KeyError.__str__(self)}" + ) + + return self._description + + @description.setter + def description(self, value: str) -> None: + self._description = value + + +class ClientDisconnected(BadRequest): + """Internal exception that is raised if Werkzeug detects a disconnected + client. Since the client is already gone at that point attempting to + send the error message to the client might not work and might ultimately + result in another exception in the server. Mainly this is here so that + it is silenced by default as far as Werkzeug is concerned. + + Since disconnections cannot be reliably detected and are unspecified + by WSGI to a large extent this might or might not be raised if a client + is gone. + + .. versionadded:: 0.8 + """ + + +class SecurityError(BadRequest): + """Raised if something triggers a security error. This is otherwise + exactly like a bad request error. + + .. versionadded:: 0.9 + """ + + +class BadHost(BadRequest): + """Raised if the submitted host is badly formatted. + + .. versionadded:: 0.11.2 + """ + + +class Unauthorized(HTTPException): + """*401* ``Unauthorized`` + + Raise if the user is not authorized to access a resource. + + The ``www_authenticate`` argument should be used to set the + ``WWW-Authenticate`` header. This is used for HTTP basic auth and + other schemes. Use :class:`~werkzeug.datastructures.WWWAuthenticate` + to create correctly formatted values. Strictly speaking a 401 + response is invalid if it doesn't provide at least one value for + this header, although real clients typically don't care. + + :param description: Override the default message used for the body + of the response. + :param www-authenticate: A single value, or list of values, for the + WWW-Authenticate header(s). + + .. versionchanged:: 2.0 + Serialize multiple ``www_authenticate`` items into multiple + ``WWW-Authenticate`` headers, rather than joining them + into a single value, for better interoperability. + + .. versionchanged:: 0.15.3 + If the ``www_authenticate`` argument is not set, the + ``WWW-Authenticate`` header is not set. + + .. versionchanged:: 0.15.3 + The ``response`` argument was restored. + + .. versionchanged:: 0.15.1 + ``description`` was moved back as the first argument, restoring + its previous position. + + .. versionchanged:: 0.15.0 + ``www_authenticate`` was added as the first argument, ahead of + ``description``. + """ + + code = 401 + description = ( + "The server could not verify that you are authorized to access" + " the URL requested. You either supplied the wrong credentials" + " (e.g. a bad password), or your browser doesn't understand" + " how to supply the credentials required." + ) + + def __init__( + self, + description: t.Optional[str] = None, + response: t.Optional["Response"] = None, + www_authenticate: t.Optional[ + t.Union["WWWAuthenticate", t.Iterable["WWWAuthenticate"]] + ] = None, + ) -> None: + super().__init__(description, response) + + from .datastructures import WWWAuthenticate + + if isinstance(www_authenticate, WWWAuthenticate): + www_authenticate = (www_authenticate,) + + self.www_authenticate = www_authenticate + + def get_headers( + self, + environ: t.Optional["WSGIEnvironment"] = None, + scope: t.Optional[dict] = None, + ) -> t.List[t.Tuple[str, str]]: + headers = super().get_headers(environ, scope) + if self.www_authenticate: + headers.extend(("WWW-Authenticate", str(x)) for x in self.www_authenticate) + return headers + + +class Forbidden(HTTPException): + """*403* `Forbidden` + + Raise if the user doesn't have the permission for the requested resource + but was authenticated. + """ + + code = 403 + description = ( + "You don't have the permission to access the requested" + " resource. It is either read-protected or not readable by the" + " server." + ) + + +class NotFound(HTTPException): + """*404* `Not Found` + + Raise if a resource does not exist and never existed. + """ + + code = 404 + description = ( + "The requested URL was not found on the server. If you entered" + " the URL manually please check your spelling and try again." + ) + + +class MethodNotAllowed(HTTPException): + """*405* `Method Not Allowed` + + Raise if the server used a method the resource does not handle. For + example `POST` if the resource is view only. Especially useful for REST. + + The first argument for this exception should be a list of allowed methods. + Strictly speaking the response would be invalid if you don't provide valid + methods in the header which you can do with that list. + """ + + code = 405 + description = "The method is not allowed for the requested URL." + + def __init__( + self, + valid_methods: t.Optional[t.Iterable[str]] = None, + description: t.Optional[str] = None, + response: t.Optional["Response"] = None, + ) -> None: + """Takes an optional list of valid http methods + starting with werkzeug 0.3 the list will be mandatory.""" + super().__init__(description=description, response=response) + self.valid_methods = valid_methods + + def get_headers( + self, + environ: t.Optional["WSGIEnvironment"] = None, + scope: t.Optional[dict] = None, + ) -> t.List[t.Tuple[str, str]]: + headers = super().get_headers(environ, scope) + if self.valid_methods: + headers.append(("Allow", ", ".join(self.valid_methods))) + return headers + + +class NotAcceptable(HTTPException): + """*406* `Not Acceptable` + + Raise if the server can't return any content conforming to the + `Accept` headers of the client. + """ + + code = 406 + description = ( + "The resource identified by the request is only capable of" + " generating response entities which have content" + " characteristics not acceptable according to the accept" + " headers sent in the request." + ) + + +class RequestTimeout(HTTPException): + """*408* `Request Timeout` + + Raise to signalize a timeout. + """ + + code = 408 + description = ( + "The server closed the network connection because the browser" + " didn't finish the request within the specified time." + ) + + +class Conflict(HTTPException): + """*409* `Conflict` + + Raise to signal that a request cannot be completed because it conflicts + with the current state on the server. + + .. versionadded:: 0.7 + """ + + code = 409 + description = ( + "A conflict happened while processing the request. The" + " resource might have been modified while the request was being" + " processed." + ) + + +class Gone(HTTPException): + """*410* `Gone` + + Raise if a resource existed previously and went away without new location. + """ + + code = 410 + description = ( + "The requested URL is no longer available on this server and" + " there is no forwarding address. If you followed a link from a" + " foreign page, please contact the author of this page." + ) + + +class LengthRequired(HTTPException): + """*411* `Length Required` + + Raise if the browser submitted data but no ``Content-Length`` header which + is required for the kind of processing the server does. + """ + + code = 411 + description = ( + "A request with this method requires a valid Content-" + "Length header." + ) + + +class PreconditionFailed(HTTPException): + """*412* `Precondition Failed` + + Status code used in combination with ``If-Match``, ``If-None-Match``, or + ``If-Unmodified-Since``. + """ + + code = 412 + description = ( + "The precondition on the request for the URL failed positive evaluation." + ) + + +class RequestEntityTooLarge(HTTPException): + """*413* `Request Entity Too Large` + + The status code one should return if the data submitted exceeded a given + limit. + """ + + code = 413 + description = "The data value transmitted exceeds the capacity limit." + + +class RequestURITooLarge(HTTPException): + """*414* `Request URI Too Large` + + Like *413* but for too long URLs. + """ + + code = 414 + description = ( + "The length of the requested URL exceeds the capacity limit for" + " this server. The request cannot be processed." + ) + + +class UnsupportedMediaType(HTTPException): + """*415* `Unsupported Media Type` + + The status code returned if the server is unable to handle the media type + the client transmitted. + """ + + code = 415 + description = ( + "The server does not support the media type transmitted in the request." + ) + + +class RequestedRangeNotSatisfiable(HTTPException): + """*416* `Requested Range Not Satisfiable` + + The client asked for an invalid part of the file. + + .. versionadded:: 0.7 + """ + + code = 416 + description = "The server cannot provide the requested range." + + def __init__( + self, + length: t.Optional[int] = None, + units: str = "bytes", + description: t.Optional[str] = None, + response: t.Optional["Response"] = None, + ) -> None: + """Takes an optional `Content-Range` header value based on ``length`` + parameter. + """ + super().__init__(description=description, response=response) + self.length = length + self.units = units + + def get_headers( + self, + environ: t.Optional["WSGIEnvironment"] = None, + scope: t.Optional[dict] = None, + ) -> t.List[t.Tuple[str, str]]: + headers = super().get_headers(environ, scope) + if self.length is not None: + headers.append(("Content-Range", f"{self.units} */{self.length}")) + return headers + + +class ExpectationFailed(HTTPException): + """*417* `Expectation Failed` + + The server cannot meet the requirements of the Expect request-header. + + .. versionadded:: 0.7 + """ + + code = 417 + description = "The server could not meet the requirements of the Expect header" + + +class ImATeapot(HTTPException): + """*418* `I'm a teapot` + + The server should return this if it is a teapot and someone attempted + to brew coffee with it. + + .. versionadded:: 0.7 + """ + + code = 418 + description = "This server is a teapot, not a coffee machine" + + +class UnprocessableEntity(HTTPException): + """*422* `Unprocessable Entity` + + Used if the request is well formed, but the instructions are otherwise + incorrect. + """ + + code = 422 + description = ( + "The request was well-formed but was unable to be followed due" + " to semantic errors." + ) + + +class Locked(HTTPException): + """*423* `Locked` + + Used if the resource that is being accessed is locked. + """ + + code = 423 + description = "The resource that is being accessed is locked." + + +class FailedDependency(HTTPException): + """*424* `Failed Dependency` + + Used if the method could not be performed on the resource + because the requested action depended on another action and that action failed. + """ + + code = 424 + description = ( + "The method could not be performed on the resource because the" + " requested action depended on another action and that action" + " failed." + ) + + +class PreconditionRequired(HTTPException): + """*428* `Precondition Required` + + The server requires this request to be conditional, typically to prevent + the lost update problem, which is a race condition between two or more + clients attempting to update a resource through PUT or DELETE. By requiring + each client to include a conditional header ("If-Match" or "If-Unmodified- + Since") with the proper value retained from a recent GET request, the + server ensures that each client has at least seen the previous revision of + the resource. + """ + + code = 428 + description = ( + "This request is required to be conditional; try using" + ' "If-Match" or "If-Unmodified-Since".' + ) + + +class _RetryAfter(HTTPException): + """Adds an optional ``retry_after`` parameter which will set the + ``Retry-After`` header. May be an :class:`int` number of seconds or + a :class:`~datetime.datetime`. + """ + + def __init__( + self, + description: t.Optional[str] = None, + response: t.Optional["Response"] = None, + retry_after: t.Optional[t.Union[datetime, int]] = None, + ) -> None: + super().__init__(description, response) + self.retry_after = retry_after + + def get_headers( + self, + environ: t.Optional["WSGIEnvironment"] = None, + scope: t.Optional[dict] = None, + ) -> t.List[t.Tuple[str, str]]: + headers = super().get_headers(environ, scope) + + if self.retry_after: + if isinstance(self.retry_after, datetime): + from .http import http_date + + value = http_date(self.retry_after) + else: + value = str(self.retry_after) + + headers.append(("Retry-After", value)) + + return headers + + +class TooManyRequests(_RetryAfter): + """*429* `Too Many Requests` + + The server is limiting the rate at which this user receives + responses, and this request exceeds that rate. (The server may use + any convenient method to identify users and their request rates). + The server may include a "Retry-After" header to indicate how long + the user should wait before retrying. + + :param retry_after: If given, set the ``Retry-After`` header to this + value. May be an :class:`int` number of seconds or a + :class:`~datetime.datetime`. + + .. versionchanged:: 1.0 + Added ``retry_after`` parameter. + """ + + code = 429 + description = "This user has exceeded an allotted request count. Try again later." + + +class RequestHeaderFieldsTooLarge(HTTPException): + """*431* `Request Header Fields Too Large` + + The server refuses to process the request because the header fields are too + large. One or more individual fields may be too large, or the set of all + headers is too large. + """ + + code = 431 + description = "One or more header fields exceeds the maximum size." + + +class UnavailableForLegalReasons(HTTPException): + """*451* `Unavailable For Legal Reasons` + + This status code indicates that the server is denying access to the + resource as a consequence of a legal demand. + """ + + code = 451 + description = "Unavailable for legal reasons." + + +class InternalServerError(HTTPException): + """*500* `Internal Server Error` + + Raise if an internal server error occurred. This is a good fallback if an + unknown error occurred in the dispatcher. + + .. versionchanged:: 1.0.0 + Added the :attr:`original_exception` attribute. + """ + + code = 500 + description = ( + "The server encountered an internal error and was unable to" + " complete your request. Either the server is overloaded or" + " there is an error in the application." + ) + + def __init__( + self, + description: t.Optional[str] = None, + response: t.Optional["Response"] = None, + original_exception: t.Optional[BaseException] = None, + ) -> None: + #: The original exception that caused this 500 error. Can be + #: used by frameworks to provide context when handling + #: unexpected errors. + self.original_exception = original_exception + super().__init__(description=description, response=response) + + +class NotImplemented(HTTPException): + """*501* `Not Implemented` + + Raise if the application does not support the action requested by the + browser. + """ + + code = 501 + description = "The server does not support the action requested by the browser." + + +class BadGateway(HTTPException): + """*502* `Bad Gateway` + + If you do proxying in your application you should return this status code + if you received an invalid response from the upstream server it accessed + in attempting to fulfill the request. + """ + + code = 502 + description = ( + "The proxy server received an invalid response from an upstream server." + ) + + +class ServiceUnavailable(_RetryAfter): + """*503* `Service Unavailable` + + Status code you should return if a service is temporarily + unavailable. + + :param retry_after: If given, set the ``Retry-After`` header to this + value. May be an :class:`int` number of seconds or a + :class:`~datetime.datetime`. + + .. versionchanged:: 1.0 + Added ``retry_after`` parameter. + """ + + code = 503 + description = ( + "The server is temporarily unable to service your request due" + " to maintenance downtime or capacity problems. Please try" + " again later." + ) + + +class GatewayTimeout(HTTPException): + """*504* `Gateway Timeout` + + Status code you should return if a connection to an upstream server + times out. + """ + + code = 504 + description = "The connection to an upstream server timed out." + + +class HTTPVersionNotSupported(HTTPException): + """*505* `HTTP Version Not Supported` + + The server does not support the HTTP protocol version used in the request. + """ + + code = 505 + description = ( + "The server does not support the HTTP protocol version used in the request." + ) + + +default_exceptions: t.Dict[int, t.Type[HTTPException]] = {} + + +def _find_exceptions() -> None: + for obj in globals().values(): + try: + is_http_exception = issubclass(obj, HTTPException) + except TypeError: + is_http_exception = False + if not is_http_exception or obj.code is None: + continue + old_obj = default_exceptions.get(obj.code, None) + if old_obj is not None and issubclass(obj, old_obj): + continue + default_exceptions[obj.code] = obj + + +_find_exceptions() +del _find_exceptions + + +class Aborter: + """When passed a dict of code -> exception items it can be used as + callable that raises exceptions. If the first argument to the + callable is an integer it will be looked up in the mapping, if it's + a WSGI application it will be raised in a proxy exception. + + The rest of the arguments are forwarded to the exception constructor. + """ + + def __init__( + self, + mapping: t.Optional[t.Dict[int, t.Type[HTTPException]]] = None, + extra: t.Optional[t.Dict[int, t.Type[HTTPException]]] = None, + ) -> None: + if mapping is None: + mapping = default_exceptions + self.mapping = dict(mapping) + if extra is not None: + self.mapping.update(extra) + + def __call__( + self, code: t.Union[int, "Response"], *args: t.Any, **kwargs: t.Any + ) -> "te.NoReturn": + from .sansio.response import Response + + if isinstance(code, Response): + raise HTTPException(response=code) + + if code not in self.mapping: + raise LookupError(f"no exception for {code!r}") + + raise self.mapping[code](*args, **kwargs) + + +def abort( + status: t.Union[int, "Response"], *args: t.Any, **kwargs: t.Any +) -> "te.NoReturn": + """Raises an :py:exc:`HTTPException` for the given status code or WSGI + application. + + If a status code is given, it will be looked up in the list of + exceptions and will raise that exception. If passed a WSGI application, + it will wrap it in a proxy WSGI exception and raise that:: + + abort(404) # 404 Not Found + abort(Response('Hello World')) + + """ + _aborter(status, *args, **kwargs) + + +_aborter: Aborter = Aborter() diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/filesystem.py b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/filesystem.py new file mode 100644 index 000000000..36a3d12e9 --- /dev/null +++ b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/filesystem.py @@ -0,0 +1,55 @@ +import codecs +import sys +import typing as t +import warnings + +# We do not trust traditional unixes. +has_likely_buggy_unicode_filesystem = ( + sys.platform.startswith("linux") or "bsd" in sys.platform +) + + +def _is_ascii_encoding(encoding: t.Optional[str]) -> bool: + """Given an encoding this figures out if the encoding is actually ASCII (which + is something we don't actually want in most cases). This is necessary + because ASCII comes under many names such as ANSI_X3.4-1968. + """ + if encoding is None: + return False + try: + return codecs.lookup(encoding).name == "ascii" + except LookupError: + return False + + +class BrokenFilesystemWarning(RuntimeWarning, UnicodeWarning): + """The warning used by Werkzeug to signal a broken filesystem. Will only be + used once per runtime.""" + + +_warned_about_filesystem_encoding = False + + +def get_filesystem_encoding() -> str: + """Returns the filesystem encoding that should be used. Note that this is + different from the Python understanding of the filesystem encoding which + might be deeply flawed. Do not use this value against Python's string APIs + because it might be different. See :ref:`filesystem-encoding` for the exact + behavior. + + The concept of a filesystem encoding in generally is not something you + should rely on. As such if you ever need to use this function except for + writing wrapper code reconsider. + """ + global _warned_about_filesystem_encoding + rv = sys.getfilesystemencoding() + if has_likely_buggy_unicode_filesystem and not rv or _is_ascii_encoding(rv): + if not _warned_about_filesystem_encoding: + warnings.warn( + "Detected a misconfigured UNIX filesystem: Will use" + f" UTF-8 as filesystem encoding instead of {rv!r}", + BrokenFilesystemWarning, + ) + _warned_about_filesystem_encoding = True + return "utf-8" + return rv diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/formparser.py b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/formparser.py new file mode 100644 index 000000000..2dcb709fc --- /dev/null +++ b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/formparser.py @@ -0,0 +1,495 @@ +import typing as t +import warnings +from functools import update_wrapper +from io import BytesIO +from itertools import chain +from typing import Union + +from . import exceptions +from ._internal import _to_str +from .datastructures import FileStorage +from .datastructures import Headers +from .datastructures import MultiDict +from .http import parse_options_header +from .sansio.multipart import Data +from .sansio.multipart import Epilogue +from .sansio.multipart import Field +from .sansio.multipart import File +from .sansio.multipart import MultipartDecoder +from .sansio.multipart import NeedData +from .urls import url_decode_stream +from .wsgi import _make_chunk_iter +from .wsgi import get_content_length +from .wsgi import get_input_stream + +# there are some platforms where SpooledTemporaryFile is not available. +# In that case we need to provide a fallback. +try: + from tempfile import SpooledTemporaryFile +except ImportError: + from tempfile import TemporaryFile + + SpooledTemporaryFile = None # type: ignore + +if t.TYPE_CHECKING: + import typing as te + from _typeshed.wsgi import WSGIEnvironment + + t_parse_result = t.Tuple[t.BinaryIO, MultiDict, MultiDict] + + class TStreamFactory(te.Protocol): + def __call__( + self, + total_content_length: t.Optional[int], + content_type: t.Optional[str], + filename: t.Optional[str], + content_length: t.Optional[int] = None, + ) -> t.BinaryIO: + ... + + +F = t.TypeVar("F", bound=t.Callable[..., t.Any]) + + +def _exhaust(stream: t.BinaryIO) -> None: + bts = stream.read(64 * 1024) + while bts: + bts = stream.read(64 * 1024) + + +def default_stream_factory( + total_content_length: t.Optional[int], + content_type: t.Optional[str], + filename: t.Optional[str], + content_length: t.Optional[int] = None, +) -> t.BinaryIO: + max_size = 1024 * 500 + + if SpooledTemporaryFile is not None: + return t.cast(t.BinaryIO, SpooledTemporaryFile(max_size=max_size, mode="rb+")) + elif total_content_length is None or total_content_length > max_size: + return t.cast(t.BinaryIO, TemporaryFile("rb+")) + + return BytesIO() + + +def parse_form_data( + environ: "WSGIEnvironment", + stream_factory: t.Optional["TStreamFactory"] = None, + charset: str = "utf-8", + errors: str = "replace", + max_form_memory_size: t.Optional[int] = None, + max_content_length: t.Optional[int] = None, + cls: t.Optional[t.Type[MultiDict]] = None, + silent: bool = True, +) -> "t_parse_result": + """Parse the form data in the environ and return it as tuple in the form + ``(stream, form, files)``. You should only call this method if the + transport method is `POST`, `PUT`, or `PATCH`. + + If the mimetype of the data transmitted is `multipart/form-data` the + files multidict will be filled with `FileStorage` objects. If the + mimetype is unknown the input stream is wrapped and returned as first + argument, else the stream is empty. + + This is a shortcut for the common usage of :class:`FormDataParser`. + + Have a look at :doc:`/request_data` for more details. + + .. versionadded:: 0.5 + The `max_form_memory_size`, `max_content_length` and + `cls` parameters were added. + + .. versionadded:: 0.5.1 + The optional `silent` flag was added. + + :param environ: the WSGI environment to be used for parsing. + :param stream_factory: An optional callable that returns a new read and + writeable file descriptor. This callable works + the same as :meth:`Response._get_file_stream`. + :param charset: The character set for URL and url encoded form data. + :param errors: The encoding error behavior. + :param max_form_memory_size: the maximum number of bytes to be accepted for + in-memory stored form data. If the data + exceeds the value specified an + :exc:`~exceptions.RequestEntityTooLarge` + exception is raised. + :param max_content_length: If this is provided and the transmitted data + is longer than this value an + :exc:`~exceptions.RequestEntityTooLarge` + exception is raised. + :param cls: an optional dict class to use. If this is not specified + or `None` the default :class:`MultiDict` is used. + :param silent: If set to False parsing errors will not be caught. + :return: A tuple in the form ``(stream, form, files)``. + """ + return FormDataParser( + stream_factory, + charset, + errors, + max_form_memory_size, + max_content_length, + cls, + silent, + ).parse_from_environ(environ) + + +def exhaust_stream(f: F) -> F: + """Helper decorator for methods that exhausts the stream on return.""" + + def wrapper(self, stream, *args, **kwargs): # type: ignore + try: + return f(self, stream, *args, **kwargs) + finally: + exhaust = getattr(stream, "exhaust", None) + + if exhaust is not None: + exhaust() + else: + while True: + chunk = stream.read(1024 * 64) + + if not chunk: + break + + return update_wrapper(t.cast(F, wrapper), f) + + +class FormDataParser: + """This class implements parsing of form data for Werkzeug. By itself + it can parse multipart and url encoded form data. It can be subclassed + and extended but for most mimetypes it is a better idea to use the + untouched stream and expose it as separate attributes on a request + object. + + .. versionadded:: 0.8 + + :param stream_factory: An optional callable that returns a new read and + writeable file descriptor. This callable works + the same as :meth:`Response._get_file_stream`. + :param charset: The character set for URL and url encoded form data. + :param errors: The encoding error behavior. + :param max_form_memory_size: the maximum number of bytes to be accepted for + in-memory stored form data. If the data + exceeds the value specified an + :exc:`~exceptions.RequestEntityTooLarge` + exception is raised. + :param max_content_length: If this is provided and the transmitted data + is longer than this value an + :exc:`~exceptions.RequestEntityTooLarge` + exception is raised. + :param cls: an optional dict class to use. If this is not specified + or `None` the default :class:`MultiDict` is used. + :param silent: If set to False parsing errors will not be caught. + """ + + def __init__( + self, + stream_factory: t.Optional["TStreamFactory"] = None, + charset: str = "utf-8", + errors: str = "replace", + max_form_memory_size: t.Optional[int] = None, + max_content_length: t.Optional[int] = None, + cls: t.Optional[t.Type[MultiDict]] = None, + silent: bool = True, + ) -> None: + if stream_factory is None: + stream_factory = default_stream_factory + + self.stream_factory = stream_factory + self.charset = charset + self.errors = errors + self.max_form_memory_size = max_form_memory_size + self.max_content_length = max_content_length + + if cls is None: + cls = MultiDict + + self.cls = cls + self.silent = silent + + def get_parse_func( + self, mimetype: str, options: t.Dict[str, str] + ) -> t.Optional[ + t.Callable[ + ["FormDataParser", t.BinaryIO, str, t.Optional[int], t.Dict[str, str]], + "t_parse_result", + ] + ]: + return self.parse_functions.get(mimetype) + + def parse_from_environ(self, environ: "WSGIEnvironment") -> "t_parse_result": + """Parses the information from the environment as form data. + + :param environ: the WSGI environment to be used for parsing. + :return: A tuple in the form ``(stream, form, files)``. + """ + content_type = environ.get("CONTENT_TYPE", "") + content_length = get_content_length(environ) + mimetype, options = parse_options_header(content_type) + return self.parse(get_input_stream(environ), mimetype, content_length, options) + + def parse( + self, + stream: t.BinaryIO, + mimetype: str, + content_length: t.Optional[int], + options: t.Optional[t.Dict[str, str]] = None, + ) -> "t_parse_result": + """Parses the information from the given stream, mimetype, + content length and mimetype parameters. + + :param stream: an input stream + :param mimetype: the mimetype of the data + :param content_length: the content length of the incoming data + :param options: optional mimetype parameters (used for + the multipart boundary for instance) + :return: A tuple in the form ``(stream, form, files)``. + """ + if ( + self.max_content_length is not None + and content_length is not None + and content_length > self.max_content_length + ): + # if the input stream is not exhausted, firefox reports Connection Reset + _exhaust(stream) + raise exceptions.RequestEntityTooLarge() + + if options is None: + options = {} + + parse_func = self.get_parse_func(mimetype, options) + + if parse_func is not None: + try: + return parse_func(self, stream, mimetype, content_length, options) + except ValueError: + if not self.silent: + raise + + return stream, self.cls(), self.cls() + + @exhaust_stream + def _parse_multipart( + self, + stream: t.BinaryIO, + mimetype: str, + content_length: t.Optional[int], + options: t.Dict[str, str], + ) -> "t_parse_result": + parser = MultiPartParser( + self.stream_factory, + self.charset, + self.errors, + max_form_memory_size=self.max_form_memory_size, + cls=self.cls, + ) + boundary = options.get("boundary", "").encode("ascii") + + if not boundary: + raise ValueError("Missing boundary") + + form, files = parser.parse(stream, boundary, content_length) + return stream, form, files + + @exhaust_stream + def _parse_urlencoded( + self, + stream: t.BinaryIO, + mimetype: str, + content_length: t.Optional[int], + options: t.Dict[str, str], + ) -> "t_parse_result": + if ( + self.max_form_memory_size is not None + and content_length is not None + and content_length > self.max_form_memory_size + ): + # if the input stream is not exhausted, firefox reports Connection Reset + _exhaust(stream) + raise exceptions.RequestEntityTooLarge() + + form = url_decode_stream(stream, self.charset, errors=self.errors, cls=self.cls) + return stream, form, self.cls() + + #: mapping of mimetypes to parsing functions + parse_functions: t.Dict[ + str, + t.Callable[ + ["FormDataParser", t.BinaryIO, str, t.Optional[int], t.Dict[str, str]], + "t_parse_result", + ], + ] = { + "multipart/form-data": _parse_multipart, + "application/x-www-form-urlencoded": _parse_urlencoded, + "application/x-url-encoded": _parse_urlencoded, + } + + +def _line_parse(line: str) -> t.Tuple[str, bool]: + """Removes line ending characters and returns a tuple (`stripped_line`, + `is_terminated`). + """ + if line[-2:] == "\r\n": + return line[:-2], True + + elif line[-1:] in {"\r", "\n"}: + return line[:-1], True + + return line, False + + +def parse_multipart_headers(iterable: t.Iterable[bytes]) -> Headers: + """Parses multipart headers from an iterable that yields lines (including + the trailing newline symbol). The iterable has to be newline terminated. + The iterable will stop at the line where the headers ended so it can be + further consumed. + :param iterable: iterable of strings that are newline terminated + """ + warnings.warn( + "'parse_multipart_headers' is deprecated and will be removed in" + " Werkzeug 2.1.", + DeprecationWarning, + stacklevel=2, + ) + result: t.List[t.Tuple[str, str]] = [] + + for b_line in iterable: + line = _to_str(b_line) + line, line_terminated = _line_parse(line) + + if not line_terminated: + raise ValueError("unexpected end of line in multipart header") + + if not line: + break + elif line[0] in " \t" and result: + key, value = result[-1] + result[-1] = (key, f"{value}\n {line[1:]}") + else: + parts = line.split(":", 1) + + if len(parts) == 2: + result.append((parts[0].strip(), parts[1].strip())) + + # we link the list to the headers, no need to create a copy, the + # list was not shared anyways. + return Headers(result) + + +class MultiPartParser: + def __init__( + self, + stream_factory: t.Optional["TStreamFactory"] = None, + charset: str = "utf-8", + errors: str = "replace", + max_form_memory_size: t.Optional[int] = None, + cls: t.Optional[t.Type[MultiDict]] = None, + buffer_size: int = 64 * 1024, + ) -> None: + self.charset = charset + self.errors = errors + self.max_form_memory_size = max_form_memory_size + + if stream_factory is None: + stream_factory = default_stream_factory + + self.stream_factory = stream_factory + + if cls is None: + cls = MultiDict + + self.cls = cls + + self.buffer_size = buffer_size + + def fail(self, message: str) -> "te.NoReturn": + raise ValueError(message) + + def get_part_charset(self, headers: Headers) -> str: + # Figure out input charset for current part + content_type = headers.get("content-type") + + if content_type: + mimetype, ct_params = parse_options_header(content_type) + return ct_params.get("charset", self.charset) + + return self.charset + + def start_file_streaming( + self, event: File, total_content_length: t.Optional[int] + ) -> t.BinaryIO: + content_type = event.headers.get("content-type") + + try: + content_length = int(event.headers["content-length"]) + except (KeyError, ValueError): + content_length = 0 + + container = self.stream_factory( + total_content_length=total_content_length, + filename=event.filename, + content_type=content_type, + content_length=content_length, + ) + return container + + def parse( + self, stream: t.BinaryIO, boundary: bytes, content_length: t.Optional[int] + ) -> t.Tuple[MultiDict, MultiDict]: + container: t.Union[t.BinaryIO, t.List[bytes]] + _write: t.Callable[[bytes], t.Any] + + iterator = chain( + _make_chunk_iter( + stream, + limit=content_length, + buffer_size=self.buffer_size, + ), + [None], + ) + + parser = MultipartDecoder(boundary, self.max_form_memory_size) + + fields = [] + files = [] + + current_part: Union[Field, File] + for data in iterator: + parser.receive_data(data) + event = parser.next_event() + while not isinstance(event, (Epilogue, NeedData)): + if isinstance(event, Field): + current_part = event + container = [] + _write = container.append + elif isinstance(event, File): + current_part = event + container = self.start_file_streaming(event, content_length) + _write = container.write + elif isinstance(event, Data): + _write(event.data) + if not event.more_data: + if isinstance(current_part, Field): + value = b"".join(container).decode( + self.get_part_charset(current_part.headers), self.errors + ) + fields.append((current_part.name, value)) + else: + container = t.cast(t.BinaryIO, container) + container.seek(0) + files.append( + ( + current_part.name, + FileStorage( + container, + current_part.filename, + current_part.name, + headers=current_part.headers, + ), + ) + ) + + event = parser.next_event() + + return self.cls(fields), self.cls(files) diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/http.py b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/http.py new file mode 100644 index 000000000..ca48fe215 --- /dev/null +++ b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/http.py @@ -0,0 +1,1388 @@ +import base64 +import email.utils +import re +import typing +import typing as t +import warnings +from datetime import date +from datetime import datetime +from datetime import time +from datetime import timedelta +from datetime import timezone +from enum import Enum +from hashlib import sha1 +from time import mktime +from time import struct_time +from urllib.parse import unquote_to_bytes as _unquote +from urllib.request import parse_http_list as _parse_list_header + +from ._internal import _cookie_parse_impl +from ._internal import _cookie_quote +from ._internal import _make_cookie_domain +from ._internal import _to_bytes +from ._internal import _to_str +from ._internal import _wsgi_decoding_dance +from werkzeug._internal import _dt_as_utc + +if t.TYPE_CHECKING: + import typing_extensions as te + from _typeshed.wsgi import WSGIEnvironment + +# for explanation of "media-range", etc. see Sections 5.3.{1,2} of RFC 7231 +_accept_re = re.compile( + r""" + ( # media-range capturing-parenthesis + [^\s;,]+ # type/subtype + (?:[ \t]*;[ \t]* # ";" + (?: # parameter non-capturing-parenthesis + [^\s;,q][^\s;,]* # token that doesn't start with "q" + | # or + q[^\s;,=][^\s;,]* # token that is more than just "q" + ) + )* # zero or more parameters + ) # end of media-range + (?:[ \t]*;[ \t]*q= # weight is a "q" parameter + (\d*(?:\.\d+)?) # qvalue capturing-parentheses + [^,]* # "extension" accept params: who cares? + )? # accept params are optional + """, + re.VERBOSE, +) +_token_chars = frozenset( + "!#$%&'*+-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ^_`abcdefghijklmnopqrstuvwxyz|~" +) +_etag_re = re.compile(r'([Ww]/)?(?:"(.*?)"|(.*?))(?:\s*,\s*|$)') +_option_header_piece_re = re.compile( + r""" + ;\s*,?\s* # newlines were replaced with commas + (?P + "[^"\\]*(?:\\.[^"\\]*)*" # quoted string + | + [^\s;,=*]+ # token + ) + (?:\*(?P\d+))? # *1, optional continuation index + \s* + (?: # optionally followed by =value + (?: # equals sign, possibly with encoding + \*\s*=\s* # * indicates extended notation + (?: # optional encoding + (?P[^\s]+?) + '(?P[^\s]*?)' + )? + | + =\s* # basic notation + ) + (?P + "[^"\\]*(?:\\.[^"\\]*)*" # quoted string + | + [^;,]+ # token + )? + )? + \s* + """, + flags=re.VERBOSE, +) +_option_header_start_mime_type = re.compile(r",\s*([^;,\s]+)([;,]\s*.+)?") +_entity_headers = frozenset( + [ + "allow", + "content-encoding", + "content-language", + "content-length", + "content-location", + "content-md5", + "content-range", + "content-type", + "expires", + "last-modified", + ] +) +_hop_by_hop_headers = frozenset( + [ + "connection", + "keep-alive", + "proxy-authenticate", + "proxy-authorization", + "te", + "trailer", + "transfer-encoding", + "upgrade", + ] +) +HTTP_STATUS_CODES = { + 100: "Continue", + 101: "Switching Protocols", + 102: "Processing", + 103: "Early Hints", # see RFC 8297 + 200: "OK", + 201: "Created", + 202: "Accepted", + 203: "Non Authoritative Information", + 204: "No Content", + 205: "Reset Content", + 206: "Partial Content", + 207: "Multi Status", + 208: "Already Reported", # see RFC 5842 + 226: "IM Used", # see RFC 3229 + 300: "Multiple Choices", + 301: "Moved Permanently", + 302: "Found", + 303: "See Other", + 304: "Not Modified", + 305: "Use Proxy", + 306: "Switch Proxy", # unused + 307: "Temporary Redirect", + 308: "Permanent Redirect", + 400: "Bad Request", + 401: "Unauthorized", + 402: "Payment Required", # unused + 403: "Forbidden", + 404: "Not Found", + 405: "Method Not Allowed", + 406: "Not Acceptable", + 407: "Proxy Authentication Required", + 408: "Request Timeout", + 409: "Conflict", + 410: "Gone", + 411: "Length Required", + 412: "Precondition Failed", + 413: "Request Entity Too Large", + 414: "Request URI Too Long", + 415: "Unsupported Media Type", + 416: "Requested Range Not Satisfiable", + 417: "Expectation Failed", + 418: "I'm a teapot", # see RFC 2324 + 421: "Misdirected Request", # see RFC 7540 + 422: "Unprocessable Entity", + 423: "Locked", + 424: "Failed Dependency", + 425: "Too Early", # see RFC 8470 + 426: "Upgrade Required", + 428: "Precondition Required", # see RFC 6585 + 429: "Too Many Requests", + 431: "Request Header Fields Too Large", + 449: "Retry With", # proprietary MS extension + 451: "Unavailable For Legal Reasons", + 500: "Internal Server Error", + 501: "Not Implemented", + 502: "Bad Gateway", + 503: "Service Unavailable", + 504: "Gateway Timeout", + 505: "HTTP Version Not Supported", + 506: "Variant Also Negotiates", # see RFC 2295 + 507: "Insufficient Storage", + 508: "Loop Detected", # see RFC 5842 + 510: "Not Extended", + 511: "Network Authentication Failed", +} + + +class COEP(Enum): + """Cross Origin Embedder Policies""" + + UNSAFE_NONE = "unsafe-none" + REQUIRE_CORP = "require-corp" + + +class COOP(Enum): + """Cross Origin Opener Policies""" + + UNSAFE_NONE = "unsafe-none" + SAME_ORIGIN_ALLOW_POPUPS = "same-origin-allow-popups" + SAME_ORIGIN = "same-origin" + + +def quote_header_value( + value: t.Union[str, int], extra_chars: str = "", allow_token: bool = True +) -> str: + """Quote a header value if necessary. + + .. versionadded:: 0.5 + + :param value: the value to quote. + :param extra_chars: a list of extra characters to skip quoting. + :param allow_token: if this is enabled token values are returned + unchanged. + """ + if isinstance(value, bytes): + value = value.decode("latin1") + value = str(value) + if allow_token: + token_chars = _token_chars | set(extra_chars) + if set(value).issubset(token_chars): + return value + value = value.replace("\\", "\\\\").replace('"', '\\"') + return f'"{value}"' + + +def unquote_header_value(value: str, is_filename: bool = False) -> str: + r"""Unquotes a header value. (Reversal of :func:`quote_header_value`). + This does not use the real unquoting but what browsers are actually + using for quoting. + + .. versionadded:: 0.5 + + :param value: the header value to unquote. + :param is_filename: The value represents a filename or path. + """ + if value and value[0] == value[-1] == '"': + # this is not the real unquoting, but fixing this so that the + # RFC is met will result in bugs with internet explorer and + # probably some other browsers as well. IE for example is + # uploading files with "C:\foo\bar.txt" as filename + value = value[1:-1] + + # if this is a filename and the starting characters look like + # a UNC path, then just return the value without quotes. Using the + # replace sequence below on a UNC path has the effect of turning + # the leading double slash into a single slash and then + # _fix_ie_filename() doesn't work correctly. See #458. + if not is_filename or value[:2] != "\\\\": + return value.replace("\\\\", "\\").replace('\\"', '"') + return value + + +def dump_options_header( + header: t.Optional[str], options: t.Mapping[str, t.Optional[t.Union[str, int]]] +) -> str: + """The reverse function to :func:`parse_options_header`. + + :param header: the header to dump + :param options: a dict of options to append. + """ + segments = [] + if header is not None: + segments.append(header) + for key, value in options.items(): + if value is None: + segments.append(key) + else: + segments.append(f"{key}={quote_header_value(value)}") + return "; ".join(segments) + + +def dump_header( + iterable: t.Union[t.Dict[str, t.Union[str, int]], t.Iterable[str]], + allow_token: bool = True, +) -> str: + """Dump an HTTP header again. This is the reversal of + :func:`parse_list_header`, :func:`parse_set_header` and + :func:`parse_dict_header`. This also quotes strings that include an + equals sign unless you pass it as dict of key, value pairs. + + >>> dump_header({'foo': 'bar baz'}) + 'foo="bar baz"' + >>> dump_header(('foo', 'bar baz')) + 'foo, "bar baz"' + + :param iterable: the iterable or dict of values to quote. + :param allow_token: if set to `False` tokens as values are disallowed. + See :func:`quote_header_value` for more details. + """ + if isinstance(iterable, dict): + items = [] + for key, value in iterable.items(): + if value is None: + items.append(key) + else: + items.append( + f"{key}={quote_header_value(value, allow_token=allow_token)}" + ) + else: + items = [quote_header_value(x, allow_token=allow_token) for x in iterable] + return ", ".join(items) + + +def dump_csp_header(header: "ds.ContentSecurityPolicy") -> str: + """Dump a Content Security Policy header. + + These are structured into policies such as "default-src 'self'; + script-src 'self'". + + .. versionadded:: 1.0.0 + Support for Content Security Policy headers was added. + + """ + return "; ".join(f"{key} {value}" for key, value in header.items()) + + +def parse_list_header(value: str) -> t.List[str]: + """Parse lists as described by RFC 2068 Section 2. + + In particular, parse comma-separated lists where the elements of + the list may include quoted-strings. A quoted-string could + contain a comma. A non-quoted string could have quotes in the + middle. Quotes are removed automatically after parsing. + + It basically works like :func:`parse_set_header` just that items + may appear multiple times and case sensitivity is preserved. + + The return value is a standard :class:`list`: + + >>> parse_list_header('token, "quoted value"') + ['token', 'quoted value'] + + To create a header from the :class:`list` again, use the + :func:`dump_header` function. + + :param value: a string with a list header. + :return: :class:`list` + """ + result = [] + for item in _parse_list_header(value): + if item[:1] == item[-1:] == '"': + item = unquote_header_value(item[1:-1]) + result.append(item) + return result + + +def parse_dict_header(value: str, cls: t.Type[dict] = dict) -> t.Dict[str, str]: + """Parse lists of key, value pairs as described by RFC 2068 Section 2 and + convert them into a python dict (or any other mapping object created from + the type with a dict like interface provided by the `cls` argument): + + >>> d = parse_dict_header('foo="is a fish", bar="as well"') + >>> type(d) is dict + True + >>> sorted(d.items()) + [('bar', 'as well'), ('foo', 'is a fish')] + + If there is no value for a key it will be `None`: + + >>> parse_dict_header('key_without_value') + {'key_without_value': None} + + To create a header from the :class:`dict` again, use the + :func:`dump_header` function. + + .. versionchanged:: 0.9 + Added support for `cls` argument. + + :param value: a string with a dict header. + :param cls: callable to use for storage of parsed results. + :return: an instance of `cls` + """ + result = cls() + if isinstance(value, bytes): + value = value.decode("latin1") + for item in _parse_list_header(value): + if "=" not in item: + result[item] = None + continue + name, value = item.split("=", 1) + if value[:1] == value[-1:] == '"': + value = unquote_header_value(value[1:-1]) + result[name] = value + return result + + +@typing.overload +def parse_options_header( + value: t.Optional[str], multiple: "te.Literal[False]" = False +) -> t.Tuple[str, t.Dict[str, str]]: + ... + + +@typing.overload +def parse_options_header( + value: t.Optional[str], multiple: "te.Literal[True]" +) -> t.Tuple[t.Any, ...]: + ... + + +def parse_options_header( + value: t.Optional[str], multiple: bool = False +) -> t.Union[t.Tuple[str, t.Dict[str, str]], t.Tuple[t.Any, ...]]: + """Parse a ``Content-Type`` like header into a tuple with the content + type and the options: + + >>> parse_options_header('text/html; charset=utf8') + ('text/html', {'charset': 'utf8'}) + + This should not be used to parse ``Cache-Control`` like headers that use + a slightly different format. For these headers use the + :func:`parse_dict_header` function. + + .. versionchanged:: 0.15 + :rfc:`2231` parameter continuations are handled. + + .. versionadded:: 0.5 + + :param value: the header to parse. + :param multiple: Whether try to parse and return multiple MIME types + :return: (mimetype, options) or (mimetype, options, mimetype, options, …) + if multiple=True + """ + if not value: + return "", {} + + result: t.List[t.Any] = [] + + value = "," + value.replace("\n", ",") + while value: + match = _option_header_start_mime_type.match(value) + if not match: + break + result.append(match.group(1)) # mimetype + options: t.Dict[str, str] = {} + # Parse options + rest = match.group(2) + encoding: t.Optional[str] + continued_encoding: t.Optional[str] = None + while rest: + optmatch = _option_header_piece_re.match(rest) + if not optmatch: + break + option, count, encoding, language, option_value = optmatch.groups() + # Continuations don't have to supply the encoding after the + # first line. If we're in a continuation, track the current + # encoding to use for subsequent lines. Reset it when the + # continuation ends. + if not count: + continued_encoding = None + else: + if not encoding: + encoding = continued_encoding + continued_encoding = encoding + option = unquote_header_value(option) + if option_value is not None: + option_value = unquote_header_value(option_value, option == "filename") + if encoding is not None: + option_value = _unquote(option_value).decode(encoding) + if count: + # Continuations append to the existing value. For + # simplicity, this ignores the possibility of + # out-of-order indices, which shouldn't happen anyway. + options[option] = options.get(option, "") + option_value + else: + options[option] = option_value + rest = rest[optmatch.end() :] + result.append(options) + if multiple is False: + return tuple(result) + value = rest + + return tuple(result) if result else ("", {}) + + +_TAnyAccept = t.TypeVar("_TAnyAccept", bound="ds.Accept") + + +@typing.overload +def parse_accept_header(value: t.Optional[str]) -> "ds.Accept": + ... + + +@typing.overload +def parse_accept_header( + value: t.Optional[str], cls: t.Type[_TAnyAccept] +) -> _TAnyAccept: + ... + + +def parse_accept_header( + value: t.Optional[str], cls: t.Optional[t.Type[_TAnyAccept]] = None +) -> _TAnyAccept: + """Parses an HTTP Accept-* header. This does not implement a complete + valid algorithm but one that supports at least value and quality + extraction. + + Returns a new :class:`Accept` object (basically a list of ``(value, quality)`` + tuples sorted by the quality with some additional accessor methods). + + The second parameter can be a subclass of :class:`Accept` that is created + with the parsed values and returned. + + :param value: the accept header string to be parsed. + :param cls: the wrapper class for the return value (can be + :class:`Accept` or a subclass thereof) + :return: an instance of `cls`. + """ + if cls is None: + cls = t.cast(t.Type[_TAnyAccept], ds.Accept) + + if not value: + return cls(None) + + result = [] + for match in _accept_re.finditer(value): + quality_match = match.group(2) + if not quality_match: + quality: float = 1 + else: + quality = max(min(float(quality_match), 1), 0) + result.append((match.group(1), quality)) + return cls(result) + + +_TAnyCC = t.TypeVar("_TAnyCC", bound="ds._CacheControl") +_t_cc_update = t.Optional[t.Callable[[_TAnyCC], None]] + + +@typing.overload +def parse_cache_control_header( + value: t.Optional[str], on_update: _t_cc_update, cls: None = None +) -> "ds.RequestCacheControl": + ... + + +@typing.overload +def parse_cache_control_header( + value: t.Optional[str], on_update: _t_cc_update, cls: t.Type[_TAnyCC] +) -> _TAnyCC: + ... + + +def parse_cache_control_header( + value: t.Optional[str], + on_update: _t_cc_update = None, + cls: t.Optional[t.Type[_TAnyCC]] = None, +) -> _TAnyCC: + """Parse a cache control header. The RFC differs between response and + request cache control, this method does not. It's your responsibility + to not use the wrong control statements. + + .. versionadded:: 0.5 + The `cls` was added. If not specified an immutable + :class:`~werkzeug.datastructures.RequestCacheControl` is returned. + + :param value: a cache control header to be parsed. + :param on_update: an optional callable that is called every time a value + on the :class:`~werkzeug.datastructures.CacheControl` + object is changed. + :param cls: the class for the returned object. By default + :class:`~werkzeug.datastructures.RequestCacheControl` is used. + :return: a `cls` object. + """ + if cls is None: + cls = t.cast(t.Type[_TAnyCC], ds.RequestCacheControl) + + if not value: + return cls((), on_update) + + return cls(parse_dict_header(value), on_update) + + +_TAnyCSP = t.TypeVar("_TAnyCSP", bound="ds.ContentSecurityPolicy") +_t_csp_update = t.Optional[t.Callable[[_TAnyCSP], None]] + + +@typing.overload +def parse_csp_header( + value: t.Optional[str], on_update: _t_csp_update, cls: None = None +) -> "ds.ContentSecurityPolicy": + ... + + +@typing.overload +def parse_csp_header( + value: t.Optional[str], on_update: _t_csp_update, cls: t.Type[_TAnyCSP] +) -> _TAnyCSP: + ... + + +def parse_csp_header( + value: t.Optional[str], + on_update: _t_csp_update = None, + cls: t.Optional[t.Type[_TAnyCSP]] = None, +) -> _TAnyCSP: + """Parse a Content Security Policy header. + + .. versionadded:: 1.0.0 + Support for Content Security Policy headers was added. + + :param value: a csp header to be parsed. + :param on_update: an optional callable that is called every time a value + on the object is changed. + :param cls: the class for the returned object. By default + :class:`~werkzeug.datastructures.ContentSecurityPolicy` is used. + :return: a `cls` object. + """ + if cls is None: + cls = t.cast(t.Type[_TAnyCSP], ds.ContentSecurityPolicy) + + if value is None: + return cls((), on_update) + + items = [] + + for policy in value.split(";"): + policy = policy.strip() + + # Ignore badly formatted policies (no space) + if " " in policy: + directive, value = policy.strip().split(" ", 1) + items.append((directive.strip(), value.strip())) + + return cls(items, on_update) + + +def parse_set_header( + value: t.Optional[str], + on_update: t.Optional[t.Callable[["ds.HeaderSet"], None]] = None, +) -> "ds.HeaderSet": + """Parse a set-like header and return a + :class:`~werkzeug.datastructures.HeaderSet` object: + + >>> hs = parse_set_header('token, "quoted value"') + + The return value is an object that treats the items case-insensitively + and keeps the order of the items: + + >>> 'TOKEN' in hs + True + >>> hs.index('quoted value') + 1 + >>> hs + HeaderSet(['token', 'quoted value']) + + To create a header from the :class:`HeaderSet` again, use the + :func:`dump_header` function. + + :param value: a set header to be parsed. + :param on_update: an optional callable that is called every time a + value on the :class:`~werkzeug.datastructures.HeaderSet` + object is changed. + :return: a :class:`~werkzeug.datastructures.HeaderSet` + """ + if not value: + return ds.HeaderSet(None, on_update) + return ds.HeaderSet(parse_list_header(value), on_update) + + +def parse_authorization_header( + value: t.Optional[str], +) -> t.Optional["ds.Authorization"]: + """Parse an HTTP basic/digest authorization header transmitted by the web + browser. The return value is either `None` if the header was invalid or + not given, otherwise an :class:`~werkzeug.datastructures.Authorization` + object. + + :param value: the authorization header to parse. + :return: a :class:`~werkzeug.datastructures.Authorization` object or `None`. + """ + if not value: + return None + value = _wsgi_decoding_dance(value) + try: + auth_type, auth_info = value.split(None, 1) + auth_type = auth_type.lower() + except ValueError: + return None + if auth_type == "basic": + try: + username, password = base64.b64decode(auth_info).split(b":", 1) + except Exception: + return None + try: + return ds.Authorization( + "basic", + { + "username": _to_str(username, "utf-8"), + "password": _to_str(password, "utf-8"), + }, + ) + except UnicodeDecodeError: + return None + elif auth_type == "digest": + auth_map = parse_dict_header(auth_info) + for key in "username", "realm", "nonce", "uri", "response": + if key not in auth_map: + return None + if "qop" in auth_map: + if not auth_map.get("nc") or not auth_map.get("cnonce"): + return None + return ds.Authorization("digest", auth_map) + return None + + +def parse_www_authenticate_header( + value: t.Optional[str], + on_update: t.Optional[t.Callable[["ds.WWWAuthenticate"], None]] = None, +) -> "ds.WWWAuthenticate": + """Parse an HTTP WWW-Authenticate header into a + :class:`~werkzeug.datastructures.WWWAuthenticate` object. + + :param value: a WWW-Authenticate header to parse. + :param on_update: an optional callable that is called every time a value + on the :class:`~werkzeug.datastructures.WWWAuthenticate` + object is changed. + :return: a :class:`~werkzeug.datastructures.WWWAuthenticate` object. + """ + if not value: + return ds.WWWAuthenticate(on_update=on_update) + try: + auth_type, auth_info = value.split(None, 1) + auth_type = auth_type.lower() + except (ValueError, AttributeError): + return ds.WWWAuthenticate(value.strip().lower(), on_update=on_update) + return ds.WWWAuthenticate(auth_type, parse_dict_header(auth_info), on_update) + + +def parse_if_range_header(value: t.Optional[str]) -> "ds.IfRange": + """Parses an if-range header which can be an etag or a date. Returns + a :class:`~werkzeug.datastructures.IfRange` object. + + .. versionchanged:: 2.0 + If the value represents a datetime, it is timezone-aware. + + .. versionadded:: 0.7 + """ + if not value: + return ds.IfRange() + date = parse_date(value) + if date is not None: + return ds.IfRange(date=date) + # drop weakness information + return ds.IfRange(unquote_etag(value)[0]) + + +def parse_range_header( + value: t.Optional[str], make_inclusive: bool = True +) -> t.Optional["ds.Range"]: + """Parses a range header into a :class:`~werkzeug.datastructures.Range` + object. If the header is missing or malformed `None` is returned. + `ranges` is a list of ``(start, stop)`` tuples where the ranges are + non-inclusive. + + .. versionadded:: 0.7 + """ + if not value or "=" not in value: + return None + + ranges = [] + last_end = 0 + units, rng = value.split("=", 1) + units = units.strip().lower() + + for item in rng.split(","): + item = item.strip() + if "-" not in item: + return None + if item.startswith("-"): + if last_end < 0: + return None + try: + begin = int(item) + except ValueError: + return None + end = None + last_end = -1 + elif "-" in item: + begin_str, end_str = item.split("-", 1) + begin_str = begin_str.strip() + end_str = end_str.strip() + if not begin_str.isdigit(): + return None + begin = int(begin_str) + if begin < last_end or last_end < 0: + return None + if end_str: + if not end_str.isdigit(): + return None + end = int(end_str) + 1 + if begin >= end: + return None + else: + end = None + last_end = end if end is not None else -1 + ranges.append((begin, end)) + + return ds.Range(units, ranges) + + +def parse_content_range_header( + value: t.Optional[str], + on_update: t.Optional[t.Callable[["ds.ContentRange"], None]] = None, +) -> t.Optional["ds.ContentRange"]: + """Parses a range header into a + :class:`~werkzeug.datastructures.ContentRange` object or `None` if + parsing is not possible. + + .. versionadded:: 0.7 + + :param value: a content range header to be parsed. + :param on_update: an optional callable that is called every time a value + on the :class:`~werkzeug.datastructures.ContentRange` + object is changed. + """ + if value is None: + return None + try: + units, rangedef = (value or "").strip().split(None, 1) + except ValueError: + return None + + if "/" not in rangedef: + return None + rng, length_str = rangedef.split("/", 1) + if length_str == "*": + length = None + elif length_str.isdigit(): + length = int(length_str) + else: + return None + + if rng == "*": + return ds.ContentRange(units, None, None, length, on_update=on_update) + elif "-" not in rng: + return None + + start_str, stop_str = rng.split("-", 1) + try: + start = int(start_str) + stop = int(stop_str) + 1 + except ValueError: + return None + + if is_byte_range_valid(start, stop, length): + return ds.ContentRange(units, start, stop, length, on_update=on_update) + + return None + + +def quote_etag(etag: str, weak: bool = False) -> str: + """Quote an etag. + + :param etag: the etag to quote. + :param weak: set to `True` to tag it "weak". + """ + if '"' in etag: + raise ValueError("invalid etag") + etag = f'"{etag}"' + if weak: + etag = f"W/{etag}" + return etag + + +def unquote_etag( + etag: t.Optional[str], +) -> t.Union[t.Tuple[str, bool], t.Tuple[None, None]]: + """Unquote a single etag: + + >>> unquote_etag('W/"bar"') + ('bar', True) + >>> unquote_etag('"bar"') + ('bar', False) + + :param etag: the etag identifier to unquote. + :return: a ``(etag, weak)`` tuple. + """ + if not etag: + return None, None + etag = etag.strip() + weak = False + if etag.startswith(("W/", "w/")): + weak = True + etag = etag[2:] + if etag[:1] == etag[-1:] == '"': + etag = etag[1:-1] + return etag, weak + + +def parse_etags(value: t.Optional[str]) -> "ds.ETags": + """Parse an etag header. + + :param value: the tag header to parse + :return: an :class:`~werkzeug.datastructures.ETags` object. + """ + if not value: + return ds.ETags() + strong = [] + weak = [] + end = len(value) + pos = 0 + while pos < end: + match = _etag_re.match(value, pos) + if match is None: + break + is_weak, quoted, raw = match.groups() + if raw == "*": + return ds.ETags(star_tag=True) + elif quoted: + raw = quoted + if is_weak: + weak.append(raw) + else: + strong.append(raw) + pos = match.end() + return ds.ETags(strong, weak) + + +def generate_etag(data: bytes) -> str: + """Generate an etag for some data. + + .. versionchanged:: 2.0 + Use SHA-1. MD5 may not be available in some environments. + """ + return sha1(data).hexdigest() + + +def parse_date(value: t.Optional[str]) -> t.Optional[datetime]: + """Parse an :rfc:`2822` date into a timezone-aware + :class:`datetime.datetime` object, or ``None`` if parsing fails. + + This is a wrapper for :func:`email.utils.parsedate_to_datetime`. It + returns ``None`` if parsing fails instead of raising an exception, + and always returns a timezone-aware datetime object. If the string + doesn't have timezone information, it is assumed to be UTC. + + :param value: A string with a supported date format. + + .. versionchanged:: 2.0 + Return a timezone-aware datetime object. Use + ``email.utils.parsedate_to_datetime``. + """ + if value is None: + return None + + try: + dt = email.utils.parsedate_to_datetime(value) + except (TypeError, ValueError): + return None + + if dt.tzinfo is None: + return dt.replace(tzinfo=timezone.utc) + + return dt + + +def cookie_date( + expires: t.Optional[t.Union[datetime, date, int, float, struct_time]] = None +) -> str: + """Format a datetime object or timestamp into an :rfc:`2822` date + string for ``Set-Cookie expires``. + + .. deprecated:: 2.0 + Will be removed in Werkzeug 2.1. Use :func:`http_date` instead. + """ + warnings.warn( + "'cookie_date' is deprecated and will be removed in Werkzeug" + " 2.1. Use 'http_date' instead.", + DeprecationWarning, + stacklevel=2, + ) + return http_date(expires) + + +def http_date( + timestamp: t.Optional[t.Union[datetime, date, int, float, struct_time]] = None +) -> str: + """Format a datetime object or timestamp into an :rfc:`2822` date + string. + + This is a wrapper for :func:`email.utils.format_datetime`. It + assumes naive datetime objects are in UTC instead of raising an + exception. + + :param timestamp: The datetime or timestamp to format. Defaults to + the current time. + + .. versionchanged:: 2.0 + Use ``email.utils.format_datetime``. Accept ``date`` objects. + """ + if isinstance(timestamp, date): + if not isinstance(timestamp, datetime): + # Assume plain date is midnight UTC. + timestamp = datetime.combine(timestamp, time(), tzinfo=timezone.utc) + else: + # Ensure datetime is timezone-aware. + timestamp = _dt_as_utc(timestamp) + + return email.utils.format_datetime(timestamp, usegmt=True) + + if isinstance(timestamp, struct_time): + timestamp = mktime(timestamp) + + return email.utils.formatdate(timestamp, usegmt=True) + + +def parse_age(value: t.Optional[str] = None) -> t.Optional[timedelta]: + """Parses a base-10 integer count of seconds into a timedelta. + + If parsing fails, the return value is `None`. + + :param value: a string consisting of an integer represented in base-10 + :return: a :class:`datetime.timedelta` object or `None`. + """ + if not value: + return None + try: + seconds = int(value) + except ValueError: + return None + if seconds < 0: + return None + try: + return timedelta(seconds=seconds) + except OverflowError: + return None + + +def dump_age(age: t.Optional[t.Union[timedelta, int]] = None) -> t.Optional[str]: + """Formats the duration as a base-10 integer. + + :param age: should be an integer number of seconds, + a :class:`datetime.timedelta` object, or, + if the age is unknown, `None` (default). + """ + if age is None: + return None + if isinstance(age, timedelta): + age = int(age.total_seconds()) + else: + age = int(age) + + if age < 0: + raise ValueError("age cannot be negative") + + return str(age) + + +def is_resource_modified( + environ: "WSGIEnvironment", + etag: t.Optional[str] = None, + data: t.Optional[bytes] = None, + last_modified: t.Optional[t.Union[datetime, str]] = None, + ignore_if_range: bool = True, +) -> bool: + """Convenience method for conditional requests. + + :param environ: the WSGI environment of the request to be checked. + :param etag: the etag for the response for comparison. + :param data: or alternatively the data of the response to automatically + generate an etag using :func:`generate_etag`. + :param last_modified: an optional date of the last modification. + :param ignore_if_range: If `False`, `If-Range` header will be taken into + account. + :return: `True` if the resource was modified, otherwise `False`. + + .. versionchanged:: 2.0 + SHA-1 is used to generate an etag value for the data. MD5 may + not be available in some environments. + + .. versionchanged:: 1.0.0 + The check is run for methods other than ``GET`` and ``HEAD``. + """ + if etag is None and data is not None: + etag = generate_etag(data) + elif data is not None: + raise TypeError("both data and etag given") + + unmodified = False + if isinstance(last_modified, str): + last_modified = parse_date(last_modified) + + # HTTP doesn't use microsecond, remove it to avoid false positive + # comparisons. Mark naive datetimes as UTC. + if last_modified is not None: + last_modified = _dt_as_utc(last_modified.replace(microsecond=0)) + + if_range = None + if not ignore_if_range and "HTTP_RANGE" in environ: + # https://tools.ietf.org/html/rfc7233#section-3.2 + # A server MUST ignore an If-Range header field received in a request + # that does not contain a Range header field. + if_range = parse_if_range_header(environ.get("HTTP_IF_RANGE")) + + if if_range is not None and if_range.date is not None: + modified_since: t.Optional[datetime] = if_range.date + else: + modified_since = parse_date(environ.get("HTTP_IF_MODIFIED_SINCE")) + + if modified_since and last_modified and last_modified <= modified_since: + unmodified = True + + if etag: + etag, _ = unquote_etag(etag) + etag = t.cast(str, etag) + + if if_range is not None and if_range.etag is not None: + unmodified = parse_etags(if_range.etag).contains(etag) + else: + if_none_match = parse_etags(environ.get("HTTP_IF_NONE_MATCH")) + if if_none_match: + # https://tools.ietf.org/html/rfc7232#section-3.2 + # "A recipient MUST use the weak comparison function when comparing + # entity-tags for If-None-Match" + unmodified = if_none_match.contains_weak(etag) + + # https://tools.ietf.org/html/rfc7232#section-3.1 + # "Origin server MUST use the strong comparison function when + # comparing entity-tags for If-Match" + if_match = parse_etags(environ.get("HTTP_IF_MATCH")) + if if_match: + unmodified = not if_match.is_strong(etag) + + return not unmodified + + +def remove_entity_headers( + headers: t.Union["ds.Headers", t.List[t.Tuple[str, str]]], + allowed: t.Iterable[str] = ("expires", "content-location"), +) -> None: + """Remove all entity headers from a list or :class:`Headers` object. This + operation works in-place. `Expires` and `Content-Location` headers are + by default not removed. The reason for this is :rfc:`2616` section + 10.3.5 which specifies some entity headers that should be sent. + + .. versionchanged:: 0.5 + added `allowed` parameter. + + :param headers: a list or :class:`Headers` object. + :param allowed: a list of headers that should still be allowed even though + they are entity headers. + """ + allowed = {x.lower() for x in allowed} + headers[:] = [ + (key, value) + for key, value in headers + if not is_entity_header(key) or key.lower() in allowed + ] + + +def remove_hop_by_hop_headers( + headers: t.Union["ds.Headers", t.List[t.Tuple[str, str]]] +) -> None: + """Remove all HTTP/1.1 "Hop-by-Hop" headers from a list or + :class:`Headers` object. This operation works in-place. + + .. versionadded:: 0.5 + + :param headers: a list or :class:`Headers` object. + """ + headers[:] = [ + (key, value) for key, value in headers if not is_hop_by_hop_header(key) + ] + + +def is_entity_header(header: str) -> bool: + """Check if a header is an entity header. + + .. versionadded:: 0.5 + + :param header: the header to test. + :return: `True` if it's an entity header, `False` otherwise. + """ + return header.lower() in _entity_headers + + +def is_hop_by_hop_header(header: str) -> bool: + """Check if a header is an HTTP/1.1 "Hop-by-Hop" header. + + .. versionadded:: 0.5 + + :param header: the header to test. + :return: `True` if it's an HTTP/1.1 "Hop-by-Hop" header, `False` otherwise. + """ + return header.lower() in _hop_by_hop_headers + + +def parse_cookie( + header: t.Union["WSGIEnvironment", str, bytes, None], + charset: str = "utf-8", + errors: str = "replace", + cls: t.Optional[t.Type["ds.MultiDict"]] = None, +) -> "ds.MultiDict[str, str]": + """Parse a cookie from a string or WSGI environ. + + The same key can be provided multiple times, the values are stored + in-order. The default :class:`MultiDict` will have the first value + first, and all values can be retrieved with + :meth:`MultiDict.getlist`. + + :param header: The cookie header as a string, or a WSGI environ dict + with a ``HTTP_COOKIE`` key. + :param charset: The charset for the cookie values. + :param errors: The error behavior for the charset decoding. + :param cls: A dict-like class to store the parsed cookies in. + Defaults to :class:`MultiDict`. + + .. versionchanged:: 1.0.0 + Returns a :class:`MultiDict` instead of a + ``TypeConversionDict``. + + .. versionchanged:: 0.5 + Returns a :class:`TypeConversionDict` instead of a regular dict. + The ``cls`` parameter was added. + """ + if isinstance(header, dict): + header = header.get("HTTP_COOKIE", "") + elif header is None: + header = "" + + # PEP 3333 sends headers through the environ as latin1 decoded + # strings. Encode strings back to bytes for parsing. + if isinstance(header, str): + header = header.encode("latin1", "replace") + + if cls is None: + cls = ds.MultiDict + + def _parse_pairs() -> t.Iterator[t.Tuple[str, str]]: + for key, val in _cookie_parse_impl(header): # type: ignore + key_str = _to_str(key, charset, errors, allow_none_charset=True) + + if not key_str: + continue + + val_str = _to_str(val, charset, errors, allow_none_charset=True) + yield key_str, val_str + + return cls(_parse_pairs()) + + +def dump_cookie( + key: str, + value: t.Union[bytes, str] = "", + max_age: t.Optional[t.Union[timedelta, int]] = None, + expires: t.Optional[t.Union[str, datetime, int, float]] = None, + path: t.Optional[str] = "/", + domain: t.Optional[str] = None, + secure: bool = False, + httponly: bool = False, + charset: str = "utf-8", + sync_expires: bool = True, + max_size: int = 4093, + samesite: t.Optional[str] = None, +) -> str: + """Create a Set-Cookie header without the ``Set-Cookie`` prefix. + + The return value is usually restricted to ascii as the vast majority + of values are properly escaped, but that is no guarantee. It's + tunneled through latin1 as required by :pep:`3333`. + + The return value is not ASCII safe if the key contains unicode + characters. This is technically against the specification but + happens in the wild. It's strongly recommended to not use + non-ASCII values for the keys. + + :param max_age: should be a number of seconds, or `None` (default) if + the cookie should last only as long as the client's + browser session. Additionally `timedelta` objects + are accepted, too. + :param expires: should be a `datetime` object or unix timestamp. + :param path: limits the cookie to a given path, per default it will + span the whole domain. + :param domain: Use this if you want to set a cross-domain cookie. For + example, ``domain=".example.com"`` will set a cookie + that is readable by the domain ``www.example.com``, + ``foo.example.com`` etc. Otherwise, a cookie will only + be readable by the domain that set it. + :param secure: The cookie will only be available via HTTPS + :param httponly: disallow JavaScript to access the cookie. This is an + extension to the cookie standard and probably not + supported by all browsers. + :param charset: the encoding for string values. + :param sync_expires: automatically set expires if max_age is defined + but expires not. + :param max_size: Warn if the final header value exceeds this size. The + default, 4093, should be safely `supported by most browsers + `_. Set to 0 to disable this check. + :param samesite: Limits the scope of the cookie such that it will + only be attached to requests if those requests are same-site. + + .. _`cookie`: http://browsercookielimits.squawky.net/ + + .. versionchanged:: 1.0.0 + The string ``'None'`` is accepted for ``samesite``. + """ + key = _to_bytes(key, charset) + value = _to_bytes(value, charset) + + if path is not None: + from .urls import iri_to_uri + + path = iri_to_uri(path, charset) + + domain = _make_cookie_domain(domain) + + if isinstance(max_age, timedelta): + max_age = int(max_age.total_seconds()) + + if expires is not None: + if not isinstance(expires, str): + expires = http_date(expires) + elif max_age is not None and sync_expires: + expires = http_date(datetime.now(tz=timezone.utc).timestamp() + max_age) + + if samesite is not None: + samesite = samesite.title() + + if samesite not in {"Strict", "Lax", "None"}: + raise ValueError("SameSite must be 'Strict', 'Lax', or 'None'.") + + buf = [key + b"=" + _cookie_quote(value)] + + # XXX: In theory all of these parameters that are not marked with `None` + # should be quoted. Because stdlib did not quote it before I did not + # want to introduce quoting there now. + for k, v, q in ( + (b"Domain", domain, True), + (b"Expires", expires, False), + (b"Max-Age", max_age, False), + (b"Secure", secure, None), + (b"HttpOnly", httponly, None), + (b"Path", path, False), + (b"SameSite", samesite, False), + ): + if q is None: + if v: + buf.append(k) + continue + + if v is None: + continue + + tmp = bytearray(k) + if not isinstance(v, (bytes, bytearray)): + v = _to_bytes(str(v), charset) + if q: + v = _cookie_quote(v) + tmp += b"=" + v + buf.append(bytes(tmp)) + + # The return value will be an incorrectly encoded latin1 header for + # consistency with the headers object. + rv = b"; ".join(buf) + rv = rv.decode("latin1") + + # Warn if the final value of the cookie is larger than the limit. If the + # cookie is too large, then it may be silently ignored by the browser, + # which can be quite hard to debug. + cookie_size = len(rv) + + if max_size and cookie_size > max_size: + value_size = len(value) + warnings.warn( + f"The {key.decode(charset)!r} cookie is too large: the value was" + f" {value_size} bytes but the" + f" header required {cookie_size - value_size} extra bytes. The final size" + f" was {cookie_size} bytes but the limit is {max_size} bytes. Browsers may" + f" silently ignore cookies larger than this.", + stacklevel=2, + ) + + return rv + + +def is_byte_range_valid( + start: t.Optional[int], stop: t.Optional[int], length: t.Optional[int] +) -> bool: + """Checks if a given byte content range is valid for the given length. + + .. versionadded:: 0.7 + """ + if (start is None) != (stop is None): + return False + elif start is None: + return length is None or length >= 0 + elif length is None: + return 0 <= start < stop # type: ignore + elif start >= stop: # type: ignore + return False + return 0 <= start < length + + +# circular dependencies +from . import datastructures as ds diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/local.py b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/local.py new file mode 100644 index 000000000..a5a7870e4 --- /dev/null +++ b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/local.py @@ -0,0 +1,666 @@ +import copy +import math +import operator +import sys +import typing as t +import warnings +from functools import partial +from functools import update_wrapper + +from .wsgi import ClosingIterator + +if t.TYPE_CHECKING: + from _typeshed.wsgi import StartResponse + from _typeshed.wsgi import WSGIApplication + from _typeshed.wsgi import WSGIEnvironment + +F = t.TypeVar("F", bound=t.Callable[..., t.Any]) + +try: + from greenlet import getcurrent as _get_ident +except ImportError: + from threading import get_ident as _get_ident + + +def get_ident() -> int: + warnings.warn( + "'get_ident' is deprecated and will be removed in Werkzeug" + " 2.1. Use 'greenlet.getcurrent' or 'threading.get_ident' for" + " previous behavior.", + DeprecationWarning, + stacklevel=2, + ) + return _get_ident() # type: ignore + + +class _CannotUseContextVar(Exception): + pass + + +try: + from contextvars import ContextVar + + if "gevent" in sys.modules or "eventlet" in sys.modules: + # Both use greenlet, so first check it has patched + # ContextVars, Greenlet <0.4.17 does not. + import greenlet + + greenlet_patched = getattr(greenlet, "GREENLET_USE_CONTEXT_VARS", False) + + if not greenlet_patched: + # If Gevent is used, check it has patched ContextVars, + # <20.5 does not. + try: + from gevent.monkey import is_object_patched + except ImportError: + # Gevent isn't used, but Greenlet is and hasn't patched + raise _CannotUseContextVar() + else: + if is_object_patched("threading", "local") and not is_object_patched( + "contextvars", "ContextVar" + ): + raise _CannotUseContextVar() + + +except (ImportError, _CannotUseContextVar): + + class ContextVar: # type: ignore + """A fake ContextVar based on the previous greenlet/threading + ident function. Used on Python 3.6, eventlet, and old versions + of gevent. + """ + + def __init__(self, _name: str) -> None: + self.storage: t.Dict[int, t.Dict[str, t.Any]] = {} + + def get(self, default: t.Dict[str, t.Any]) -> t.Dict[str, t.Any]: + return self.storage.get(_get_ident(), default) + + def set(self, value: t.Dict[str, t.Any]) -> None: + self.storage[_get_ident()] = value + + +def release_local(local: t.Union["Local", "LocalStack"]) -> None: + """Releases the contents of the local for the current context. + This makes it possible to use locals without a manager. + + Example:: + + >>> loc = Local() + >>> loc.foo = 42 + >>> release_local(loc) + >>> hasattr(loc, 'foo') + False + + With this function one can release :class:`Local` objects as well + as :class:`LocalStack` objects. However it is not possible to + release data held by proxies that way, one always has to retain + a reference to the underlying local object in order to be able + to release it. + + .. versionadded:: 0.6.1 + """ + local.__release_local__() + + +class Local: + __slots__ = ("_storage",) + + def __init__(self) -> None: + object.__setattr__(self, "_storage", ContextVar("local_storage")) + + @property + def __storage__(self) -> t.Dict[str, t.Any]: + warnings.warn( + "'__storage__' is deprecated and will be removed in Werkzeug 2.1.", + DeprecationWarning, + stacklevel=2, + ) + return self._storage.get({}) # type: ignore + + @property + def __ident_func__(self) -> t.Callable[[], int]: + warnings.warn( + "'__ident_func__' is deprecated and will be removed in" + " Werkzeug 2.1. It should not be used in Python 3.7+.", + DeprecationWarning, + stacklevel=2, + ) + return _get_ident # type: ignore + + @__ident_func__.setter + def __ident_func__(self, func: t.Callable[[], int]) -> None: + warnings.warn( + "'__ident_func__' is deprecated and will be removed in" + " Werkzeug 2.1. Setting it no longer has any effect.", + DeprecationWarning, + stacklevel=2, + ) + + def __iter__(self) -> t.Iterator[t.Tuple[int, t.Any]]: + return iter(self._storage.get({}).items()) + + def __call__(self, proxy: str) -> "LocalProxy": + """Create a proxy for a name.""" + return LocalProxy(self, proxy) + + def __release_local__(self) -> None: + self._storage.set({}) + + def __getattr__(self, name: str) -> t.Any: + values = self._storage.get({}) + try: + return values[name] + except KeyError: + raise AttributeError(name) + + def __setattr__(self, name: str, value: t.Any) -> None: + values = self._storage.get({}).copy() + values[name] = value + self._storage.set(values) + + def __delattr__(self, name: str) -> None: + values = self._storage.get({}).copy() + try: + del values[name] + self._storage.set(values) + except KeyError: + raise AttributeError(name) + + +class LocalStack: + """This class works similar to a :class:`Local` but keeps a stack + of objects instead. This is best explained with an example:: + + >>> ls = LocalStack() + >>> ls.push(42) + >>> ls.top + 42 + >>> ls.push(23) + >>> ls.top + 23 + >>> ls.pop() + 23 + >>> ls.top + 42 + + They can be force released by using a :class:`LocalManager` or with + the :func:`release_local` function but the correct way is to pop the + item from the stack after using. When the stack is empty it will + no longer be bound to the current context (and as such released). + + By calling the stack without arguments it returns a proxy that resolves to + the topmost item on the stack. + + .. versionadded:: 0.6.1 + """ + + def __init__(self) -> None: + self._local = Local() + + def __release_local__(self) -> None: + self._local.__release_local__() + + @property + def __ident_func__(self) -> t.Callable[[], int]: + return self._local.__ident_func__ + + @__ident_func__.setter + def __ident_func__(self, value: t.Callable[[], int]) -> None: + object.__setattr__(self._local, "__ident_func__", value) + + def __call__(self) -> "LocalProxy": + def _lookup() -> t.Any: + rv = self.top + if rv is None: + raise RuntimeError("object unbound") + return rv + + return LocalProxy(_lookup) + + def push(self, obj: t.Any) -> t.List[t.Any]: + """Pushes a new item to the stack""" + rv = getattr(self._local, "stack", []).copy() + rv.append(obj) + self._local.stack = rv + return rv # type: ignore + + def pop(self) -> t.Any: + """Removes the topmost item from the stack, will return the + old value or `None` if the stack was already empty. + """ + stack = getattr(self._local, "stack", None) + if stack is None: + return None + elif len(stack) == 1: + release_local(self._local) + return stack[-1] + else: + return stack.pop() + + @property + def top(self) -> t.Any: + """The topmost item on the stack. If the stack is empty, + `None` is returned. + """ + try: + return self._local.stack[-1] + except (AttributeError, IndexError): + return None + + +class LocalManager: + """Local objects cannot manage themselves. For that you need a local + manager. You can pass a local manager multiple locals or add them + later y appending them to `manager.locals`. Every time the manager + cleans up, it will clean up all the data left in the locals for this + context. + + .. versionchanged:: 2.0 + ``ident_func`` is deprecated and will be removed in Werkzeug + 2.1. + + .. versionchanged:: 0.6.1 + The :func:`release_local` function can be used instead of a + manager. + + .. versionchanged:: 0.7 + The ``ident_func`` parameter was added. + """ + + def __init__( + self, + locals: t.Optional[t.Iterable[t.Union[Local, LocalStack]]] = None, + ident_func: None = None, + ) -> None: + if locals is None: + self.locals = [] + elif isinstance(locals, Local): + self.locals = [locals] + else: + self.locals = list(locals) + + if ident_func is not None: + warnings.warn( + "'ident_func' is deprecated and will be removed in" + " Werkzeug 2.1. Setting it no longer has any effect.", + DeprecationWarning, + stacklevel=2, + ) + + @property + def ident_func(self) -> t.Callable[[], int]: + warnings.warn( + "'ident_func' is deprecated and will be removed in Werkzeug 2.1.", + DeprecationWarning, + stacklevel=2, + ) + return _get_ident # type: ignore + + @ident_func.setter + def ident_func(self, func: t.Callable[[], int]) -> None: + warnings.warn( + "'ident_func' is deprecated and will be removedin Werkzeug" + " 2.1. Setting it no longer has any effect.", + DeprecationWarning, + stacklevel=2, + ) + + def get_ident(self) -> int: + """Return the context identifier the local objects use internally for + this context. You cannot override this method to change the behavior + but use it to link other context local objects (such as SQLAlchemy's + scoped sessions) to the Werkzeug locals. + + .. deprecated:: 2.0 + Will be removed in Werkzeug 2.1. + + .. versionchanged:: 0.7 + You can pass a different ident function to the local manager that + will then be propagated to all the locals passed to the + constructor. + """ + warnings.warn( + "'get_ident' is deprecated and will be removed in Werkzeug 2.1.", + DeprecationWarning, + stacklevel=2, + ) + return self.ident_func() + + def cleanup(self) -> None: + """Manually clean up the data in the locals for this context. Call + this at the end of the request or use `make_middleware()`. + """ + for local in self.locals: + release_local(local) + + def make_middleware(self, app: "WSGIApplication") -> "WSGIApplication": + """Wrap a WSGI application so that cleaning up happens after + request end. + """ + + def application( + environ: "WSGIEnvironment", start_response: "StartResponse" + ) -> t.Iterable[bytes]: + return ClosingIterator(app(environ, start_response), self.cleanup) + + return application + + def middleware(self, func: "WSGIApplication") -> "WSGIApplication": + """Like `make_middleware` but for decorating functions. + + Example usage:: + + @manager.middleware + def application(environ, start_response): + ... + + The difference to `make_middleware` is that the function passed + will have all the arguments copied from the inner application + (name, docstring, module). + """ + return update_wrapper(self.make_middleware(func), func) + + def __repr__(self) -> str: + return f"<{type(self).__name__} storages: {len(self.locals)}>" + + +class _ProxyLookup: + """Descriptor that handles proxied attribute lookup for + :class:`LocalProxy`. + + :param f: The built-in function this attribute is accessed through. + Instead of looking up the special method, the function call + is redone on the object. + :param fallback: Call this method if the proxy is unbound instead of + raising a :exc:`RuntimeError`. + :param class_value: Value to return when accessed from the class. + Used for ``__doc__`` so building docs still works. + """ + + __slots__ = ("bind_f", "fallback", "class_value", "name") + + def __init__( + self, + f: t.Optional[t.Callable] = None, + fallback: t.Optional[t.Callable] = None, + class_value: t.Optional[t.Any] = None, + ) -> None: + bind_f: t.Optional[t.Callable[["LocalProxy", t.Any], t.Callable]] + + if hasattr(f, "__get__"): + # A Python function, can be turned into a bound method. + + def bind_f(instance: "LocalProxy", obj: t.Any) -> t.Callable: + return f.__get__(obj, type(obj)) # type: ignore + + elif f is not None: + # A C function, use partial to bind the first argument. + + def bind_f(instance: "LocalProxy", obj: t.Any) -> t.Callable: + return partial(f, obj) # type: ignore + + else: + # Use getattr, which will produce a bound method. + bind_f = None + + self.bind_f = bind_f + self.fallback = fallback + self.class_value = class_value + + def __set_name__(self, owner: "LocalProxy", name: str) -> None: + self.name = name + + def __get__(self, instance: "LocalProxy", owner: t.Optional[type] = None) -> t.Any: + if instance is None: + if self.class_value is not None: + return self.class_value + + return self + + try: + obj = instance._get_current_object() + except RuntimeError: + if self.fallback is None: + raise + + return self.fallback.__get__(instance, owner) # type: ignore + + if self.bind_f is not None: + return self.bind_f(instance, obj) + + return getattr(obj, self.name) + + def __repr__(self) -> str: + return f"proxy {self.name}" + + def __call__(self, instance: "LocalProxy", *args: t.Any, **kwargs: t.Any) -> t.Any: + """Support calling unbound methods from the class. For example, + this happens with ``copy.copy``, which does + ``type(x).__copy__(x)``. ``type(x)`` can't be proxied, so it + returns the proxy type and descriptor. + """ + return self.__get__(instance, type(instance))(*args, **kwargs) + + +class _ProxyIOp(_ProxyLookup): + """Look up an augmented assignment method on a proxied object. The + method is wrapped to return the proxy instead of the object. + """ + + __slots__ = () + + def __init__( + self, f: t.Optional[t.Callable] = None, fallback: t.Optional[t.Callable] = None + ) -> None: + super().__init__(f, fallback) + + def bind_f(instance: "LocalProxy", obj: t.Any) -> t.Callable: + def i_op(self: t.Any, other: t.Any) -> "LocalProxy": + f(self, other) # type: ignore + return instance + + return i_op.__get__(obj, type(obj)) # type: ignore + + self.bind_f = bind_f + + +def _l_to_r_op(op: F) -> F: + """Swap the argument order to turn an l-op into an r-op.""" + + def r_op(obj: t.Any, other: t.Any) -> t.Any: + return op(other, obj) + + return t.cast(F, r_op) + + +class LocalProxy: + """A proxy to the object bound to a :class:`Local`. All operations + on the proxy are forwarded to the bound object. If no object is + bound, a :exc:`RuntimeError` is raised. + + .. code-block:: python + + from werkzeug.local import Local + l = Local() + + # a proxy to whatever l.user is set to + user = l("user") + + from werkzeug.local import LocalStack + _request_stack = LocalStack() + + # a proxy to _request_stack.top + request = _request_stack() + + # a proxy to the session attribute of the request proxy + session = LocalProxy(lambda: request.session) + + ``__repr__`` and ``__class__`` are forwarded, so ``repr(x)`` and + ``isinstance(x, cls)`` will look like the proxied object. Use + ``issubclass(type(x), LocalProxy)`` to check if an object is a + proxy. + + .. code-block:: python + + repr(user) # + isinstance(user, User) # True + issubclass(type(user), LocalProxy) # True + + :param local: The :class:`Local` or callable that provides the + proxied object. + :param name: The attribute name to look up on a :class:`Local`. Not + used if a callable is given. + + .. versionchanged:: 2.0 + Updated proxied attributes and methods to reflect the current + data model. + + .. versionchanged:: 0.6.1 + The class can be instantiated with a callable. + """ + + __slots__ = ("__local", "__name", "__wrapped__") + + def __init__( + self, + local: t.Union["Local", t.Callable[[], t.Any]], + name: t.Optional[str] = None, + ) -> None: + object.__setattr__(self, "_LocalProxy__local", local) + object.__setattr__(self, "_LocalProxy__name", name) + + if callable(local) and not hasattr(local, "__release_local__"): + # "local" is a callable that is not an instance of Local or + # LocalManager: mark it as a wrapped function. + object.__setattr__(self, "__wrapped__", local) + + def _get_current_object(self) -> t.Any: + """Return the current object. This is useful if you want the real + object behind the proxy at a time for performance reasons or because + you want to pass the object into a different context. + """ + if not hasattr(self.__local, "__release_local__"): # type: ignore + return self.__local() # type: ignore + + try: + return getattr(self.__local, self.__name) # type: ignore + except AttributeError: + raise RuntimeError(f"no object bound to {self.__name}") # type: ignore + + __doc__ = _ProxyLookup( # type: ignore + class_value=__doc__, fallback=lambda self: type(self).__doc__ + ) + # __del__ should only delete the proxy + __repr__ = _ProxyLookup( # type: ignore + repr, fallback=lambda self: f"<{type(self).__name__} unbound>" + ) + __str__ = _ProxyLookup(str) # type: ignore + __bytes__ = _ProxyLookup(bytes) + __format__ = _ProxyLookup() # type: ignore + __lt__ = _ProxyLookup(operator.lt) + __le__ = _ProxyLookup(operator.le) + __eq__ = _ProxyLookup(operator.eq) # type: ignore + __ne__ = _ProxyLookup(operator.ne) # type: ignore + __gt__ = _ProxyLookup(operator.gt) + __ge__ = _ProxyLookup(operator.ge) + __hash__ = _ProxyLookup(hash) # type: ignore + __bool__ = _ProxyLookup(bool, fallback=lambda self: False) + __getattr__ = _ProxyLookup(getattr) + # __getattribute__ triggered through __getattr__ + __setattr__ = _ProxyLookup(setattr) # type: ignore + __delattr__ = _ProxyLookup(delattr) # type: ignore + __dir__ = _ProxyLookup(dir, fallback=lambda self: []) # type: ignore + # __get__ (proxying descriptor not supported) + # __set__ (descriptor) + # __delete__ (descriptor) + # __set_name__ (descriptor) + # __objclass__ (descriptor) + # __slots__ used by proxy itself + # __dict__ (__getattr__) + # __weakref__ (__getattr__) + # __init_subclass__ (proxying metaclass not supported) + # __prepare__ (metaclass) + __class__ = _ProxyLookup(fallback=lambda self: type(self)) # type: ignore + __instancecheck__ = _ProxyLookup(lambda self, other: isinstance(other, self)) + __subclasscheck__ = _ProxyLookup(lambda self, other: issubclass(other, self)) + # __class_getitem__ triggered through __getitem__ + __call__ = _ProxyLookup(lambda self, *args, **kwargs: self(*args, **kwargs)) + __len__ = _ProxyLookup(len) + __length_hint__ = _ProxyLookup(operator.length_hint) + __getitem__ = _ProxyLookup(operator.getitem) + __setitem__ = _ProxyLookup(operator.setitem) + __delitem__ = _ProxyLookup(operator.delitem) + # __missing__ triggered through __getitem__ + __iter__ = _ProxyLookup(iter) + __next__ = _ProxyLookup(next) + __reversed__ = _ProxyLookup(reversed) + __contains__ = _ProxyLookup(operator.contains) + __add__ = _ProxyLookup(operator.add) + __sub__ = _ProxyLookup(operator.sub) + __mul__ = _ProxyLookup(operator.mul) + __matmul__ = _ProxyLookup(operator.matmul) + __truediv__ = _ProxyLookup(operator.truediv) + __floordiv__ = _ProxyLookup(operator.floordiv) + __mod__ = _ProxyLookup(operator.mod) + __divmod__ = _ProxyLookup(divmod) + __pow__ = _ProxyLookup(pow) + __lshift__ = _ProxyLookup(operator.lshift) + __rshift__ = _ProxyLookup(operator.rshift) + __and__ = _ProxyLookup(operator.and_) + __xor__ = _ProxyLookup(operator.xor) + __or__ = _ProxyLookup(operator.or_) + __radd__ = _ProxyLookup(_l_to_r_op(operator.add)) + __rsub__ = _ProxyLookup(_l_to_r_op(operator.sub)) + __rmul__ = _ProxyLookup(_l_to_r_op(operator.mul)) + __rmatmul__ = _ProxyLookup(_l_to_r_op(operator.matmul)) + __rtruediv__ = _ProxyLookup(_l_to_r_op(operator.truediv)) + __rfloordiv__ = _ProxyLookup(_l_to_r_op(operator.floordiv)) + __rmod__ = _ProxyLookup(_l_to_r_op(operator.mod)) + __rdivmod__ = _ProxyLookup(_l_to_r_op(divmod)) + __rpow__ = _ProxyLookup(_l_to_r_op(pow)) + __rlshift__ = _ProxyLookup(_l_to_r_op(operator.lshift)) + __rrshift__ = _ProxyLookup(_l_to_r_op(operator.rshift)) + __rand__ = _ProxyLookup(_l_to_r_op(operator.and_)) + __rxor__ = _ProxyLookup(_l_to_r_op(operator.xor)) + __ror__ = _ProxyLookup(_l_to_r_op(operator.or_)) + __iadd__ = _ProxyIOp(operator.iadd) + __isub__ = _ProxyIOp(operator.isub) + __imul__ = _ProxyIOp(operator.imul) + __imatmul__ = _ProxyIOp(operator.imatmul) + __itruediv__ = _ProxyIOp(operator.itruediv) + __ifloordiv__ = _ProxyIOp(operator.ifloordiv) + __imod__ = _ProxyIOp(operator.imod) + __ipow__ = _ProxyIOp(operator.ipow) + __ilshift__ = _ProxyIOp(operator.ilshift) + __irshift__ = _ProxyIOp(operator.irshift) + __iand__ = _ProxyIOp(operator.iand) + __ixor__ = _ProxyIOp(operator.ixor) + __ior__ = _ProxyIOp(operator.ior) + __neg__ = _ProxyLookup(operator.neg) + __pos__ = _ProxyLookup(operator.pos) + __abs__ = _ProxyLookup(abs) + __invert__ = _ProxyLookup(operator.invert) + __complex__ = _ProxyLookup(complex) + __int__ = _ProxyLookup(int) + __float__ = _ProxyLookup(float) + __index__ = _ProxyLookup(operator.index) + __round__ = _ProxyLookup(round) + __trunc__ = _ProxyLookup(math.trunc) + __floor__ = _ProxyLookup(math.floor) + __ceil__ = _ProxyLookup(math.ceil) + __enter__ = _ProxyLookup() + __exit__ = _ProxyLookup() + __await__ = _ProxyLookup() + __aiter__ = _ProxyLookup() + __anext__ = _ProxyLookup() + __aenter__ = _ProxyLookup() + __aexit__ = _ProxyLookup() + __copy__ = _ProxyLookup(copy.copy) + __deepcopy__ = _ProxyLookup(copy.deepcopy) + # __getnewargs_ex__ (pickle through proxy not supported) + # __getnewargs__ (pickle) + # __getstate__ (pickle) + # __setstate__ (pickle) + # __reduce__ (pickle) + # __reduce_ex__ (pickle) diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/middleware/__init__.py b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/middleware/__init__.py new file mode 100644 index 000000000..6ddcf7f5c --- /dev/null +++ b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/middleware/__init__.py @@ -0,0 +1,22 @@ +""" +Middleware +========== + +A WSGI middleware is a WSGI application that wraps another application +in order to observe or change its behavior. Werkzeug provides some +middleware for common use cases. + +.. toctree:: + :maxdepth: 1 + + proxy_fix + shared_data + dispatcher + http_proxy + lint + profiler + +The :doc:`interactive debugger ` is also a middleware that can +be applied manually, although it is typically used automatically with +the :doc:`development server `. +""" diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/middleware/__pycache__/__init__.cpython-38.pyc b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/middleware/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..25fc4494036b2cdda5938f6bd07c25cf2c7aa467 GIT binary patch literal 715 zcmYjPv2NQi5S6nuAdr9H8laXMAWmx`HQK4@&@M?4w2OQa&mtX)BuGk@^GE%qEFC&` zYNt*;(Lw41NZh@rqwYQO%h?&rD3(9I!k<}|{hW&9AS`=FE1c3MvTvD~-DKw{@QVoc zNeQ8G2Oinm=V0tTzq|SHk$2B5SAoL;1n0DBAS!Eklo0vOgCo}1DADsHnkmDr7X(Jj zZ5_}LNC0mnm=?K;f!8QuP}b-C4*mTD_APhb4oYC)!FI@wM%YZPw(ID>eL&toz>qVR z=X87%Jz`lhPEmF+3UpDH{DLKfjvV*ZMvbWolH`g?K!j8a6&%EdhEJu8(N(D}S-nhutnJCcz+YrwD?4`!j@9he@p z-oNCaqqKc1$(^Y^?j3ne%p_TG=%ek(`UA96Q8F_9X&8Y6YU?^QkteUGQKm$hcD2sg z=kwX)i>ji*tLl5^Pv|+M?QblcR|7;W2JpqkYPzyeEb3x>JxbqRE-rW5_DU?b;=+XO zo3VRE(VF76aLUblUUp!#&1VgqkroOe`d8X7V7NBqlDvoEzx+%@HWbCz+9g*^|SeR5CN{ zt7nfH%p2=Y5Nxw3Pg9sIPkBm`VAY9=4QzlZhm|P@d?eY5f1B(C>Li=x6E@~k$#^i} z(F>UgwtWDj@#bu(a;V0V#9_hWj6LF0ei*5^Fs$+>hsQ9TseD2z1Mtx2K;DItZ>@>W4=cLESxi^4Qw<=#UL#xNFVO<9w0` zHjvB;5Nwz$aLodW61H_Lpyhg(7x8t(%%*M;uQw8cLsbNdf=x03)2l<9MBpNYd&p>T zL86`OL`1+W>D(T2!@5;PkSxU~8I>eeu7mS%=w2@f7zX6B&sKo<;7XBp1L$23W%v z3O%J-vfZPjgDSd;C2}g0&4A1zm{s1vIS4(S(T>UA0DhHK%?4Zp%D`%pS9R85NOW)k zxkY?|V4F|LBg!5VvPkk7j-JFwC$Jz34rHB|SS5Wkpd^r`+MtG`bE_&rOJImt$;c!# zHkkd|EdZm_VSHlpDB&7;d22F)pcm!EOi{dg?8BQkKVk<+sx&(2p?%0q%O3m7;r*>! zTRRD#Xc_#3Ughj>xaInx;mSOdy2!zWjd!R;td!+q>p?b+QG6+CJ)*;_qk)ev@aV^w zq|s(YE@&%yS zx>*QYVuVs@%QprtwUx4HhPM2WE~7m>{>NWinju{Krvr zr~W+c7Al^iXu4Cby2Cs{bJN{BgYM}^r(-p`edqSc@#wC&cPu{0^zp~1=}nAL);;PL zaj`YY3c4S}7VZt1Y?(VB+#B4L)9#5>FTRnJQFnO}b{8(Aj~BDD6^3yZn=rfuD>Uv| z$Ei2mZ~k1+=2#o?WHjf)EwFYLjPSa&fWkcIL zxW7j5M0|0x5Cr;bXj~?YjZFJjbF`>9<4IWYh8a)sZ-zwJ=@Q97aw0UZF#H5x`h84} z-*#(W%MI}JyesY%?~2!U+ivUkQ&veJAht;$=?TL!2*VUtOhW5+7`~kFi9{nz+mRoB( z|MNW0{e$cMx4q^$p4<9=#j~cafun4gS%Gp?6U=gYnQhyBYTh*FQG m=$z(k2F&nc^#9H_CD*FfO#^M3*N;4u#X literal 0 HcmV?d00001 diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/middleware/__pycache__/http_proxy.cpython-38.pyc b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/middleware/__pycache__/http_proxy.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2b96da88e91f2ac2b587210e08ed258aad628714 GIT binary patch literal 6835 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 Olm%Xv{lV*0&HP__&Lk25 literal 0 HcmV?d00001 diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/middleware/__pycache__/lint.cpython-38.pyc b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/middleware/__pycache__/lint.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dd59324de74b83f3e653c7d767805dce48cc6f83 GIT binary patch literal 12738 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 literal 0 HcmV?d00001 diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/middleware/__pycache__/profiler.cpython-38.pyc b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/middleware/__pycache__/profiler.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..07dc4431295b6346e752f6c38158d3e58a2720c4 GIT binary patch literal 4973 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 Q7+-#^ZnO-NJMPl|0o`;Ty8r+H literal 0 HcmV?d00001 diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/middleware/__pycache__/shared_data.cpython-38.pyc b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/middleware/__pycache__/shared_data.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..55d0949a9778fd20cf096c68d3c48d82249d3c26 GIT binary patch literal 9878 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 literal 0 HcmV?d00001 diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/middleware/dispatcher.py b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/middleware/dispatcher.py new file mode 100644 index 000000000..ace1c7504 --- /dev/null +++ b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/middleware/dispatcher.py @@ -0,0 +1,78 @@ +""" +Application Dispatcher +====================== + +This middleware creates a single WSGI application that dispatches to +multiple other WSGI applications mounted at different URL paths. + +A common example is writing a Single Page Application, where you have a +backend API and a frontend written in JavaScript that does the routing +in the browser rather than requesting different pages from the server. +The frontend is a single HTML and JS file that should be served for any +path besides "/api". + +This example dispatches to an API app under "/api", an admin app +under "/admin", and an app that serves frontend files for all other +requests:: + + app = DispatcherMiddleware(serve_frontend, { + '/api': api_app, + '/admin': admin_app, + }) + +In production, you might instead handle this at the HTTP server level, +serving files or proxying to application servers based on location. The +API and admin apps would each be deployed with a separate WSGI server, +and the static files would be served directly by the HTTP server. + +.. autoclass:: DispatcherMiddleware + +:copyright: 2007 Pallets +:license: BSD-3-Clause +""" +import typing as t + +if t.TYPE_CHECKING: + from _typeshed.wsgi import StartResponse + from _typeshed.wsgi import WSGIApplication + from _typeshed.wsgi import WSGIEnvironment + + +class DispatcherMiddleware: + """Combine multiple applications as a single WSGI application. + Requests are dispatched to an application based on the path it is + mounted under. + + :param app: The WSGI application to dispatch to if the request + doesn't match a mounted path. + :param mounts: Maps path prefixes to applications for dispatching. + """ + + def __init__( + self, + app: "WSGIApplication", + mounts: t.Optional[t.Dict[str, "WSGIApplication"]] = None, + ) -> None: + self.app = app + self.mounts = mounts or {} + + def __call__( + self, environ: "WSGIEnvironment", start_response: "StartResponse" + ) -> t.Iterable[bytes]: + script = environ.get("PATH_INFO", "") + path_info = "" + + while "/" in script: + if script in self.mounts: + app = self.mounts[script] + break + + script, last_item = script.rsplit("/", 1) + path_info = f"/{last_item}{path_info}" + else: + app = self.mounts.get(script, self.app) + + original_script_name = environ.get("SCRIPT_NAME", "") + environ["SCRIPT_NAME"] = original_script_name + script + environ["PATH_INFO"] = path_info + return app(environ, start_response) diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/middleware/http_proxy.py b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/middleware/http_proxy.py new file mode 100644 index 000000000..1cde458df --- /dev/null +++ b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/middleware/http_proxy.py @@ -0,0 +1,230 @@ +""" +Basic HTTP Proxy +================ + +.. autoclass:: ProxyMiddleware + +:copyright: 2007 Pallets +:license: BSD-3-Clause +""" +import typing as t +from http import client + +from ..datastructures import EnvironHeaders +from ..http import is_hop_by_hop_header +from ..urls import url_parse +from ..urls import url_quote +from ..wsgi import get_input_stream + +if t.TYPE_CHECKING: + from _typeshed.wsgi import StartResponse + from _typeshed.wsgi import WSGIApplication + from _typeshed.wsgi import WSGIEnvironment + + +class ProxyMiddleware: + """Proxy requests under a path to an external server, routing other + requests to the app. + + This middleware can only proxy HTTP requests, as HTTP is the only + protocol handled by the WSGI server. Other protocols, such as + WebSocket requests, cannot be proxied at this layer. This should + only be used for development, in production a real proxy server + should be used. + + The middleware takes a dict mapping a path prefix to a dict + describing the host to be proxied to:: + + app = ProxyMiddleware(app, { + "/static/": { + "target": "http://127.0.0.1:5001/", + } + }) + + Each host has the following options: + + ``target``: + The target URL to dispatch to. This is required. + ``remove_prefix``: + Whether to remove the prefix from the URL before dispatching it + to the target. The default is ``False``. + ``host``: + ``""`` (default): + The host header is automatically rewritten to the URL of the + target. + ``None``: + The host header is unmodified from the client request. + Any other value: + The host header is overwritten with the value. + ``headers``: + A dictionary of headers to be sent with the request to the + target. The default is ``{}``. + ``ssl_context``: + A :class:`ssl.SSLContext` defining how to verify requests if the + target is HTTPS. The default is ``None``. + + In the example above, everything under ``"/static/"`` is proxied to + the server on port 5001. The host header is rewritten to the target, + and the ``"/static/"`` prefix is removed from the URLs. + + :param app: The WSGI application to wrap. + :param targets: Proxy target configurations. See description above. + :param chunk_size: Size of chunks to read from input stream and + write to target. + :param timeout: Seconds before an operation to a target fails. + + .. versionadded:: 0.14 + """ + + def __init__( + self, + app: "WSGIApplication", + targets: t.Mapping[str, t.Dict[str, t.Any]], + chunk_size: int = 2 << 13, + timeout: int = 10, + ) -> None: + def _set_defaults(opts: t.Dict[str, t.Any]) -> t.Dict[str, t.Any]: + opts.setdefault("remove_prefix", False) + opts.setdefault("host", "") + opts.setdefault("headers", {}) + opts.setdefault("ssl_context", None) + return opts + + self.app = app + self.targets = { + f"/{k.strip('/')}/": _set_defaults(v) for k, v in targets.items() + } + self.chunk_size = chunk_size + self.timeout = timeout + + def proxy_to( + self, opts: t.Dict[str, t.Any], path: str, prefix: str + ) -> "WSGIApplication": + target = url_parse(opts["target"]) + host = t.cast(str, target.ascii_host) + + def application( + environ: "WSGIEnvironment", start_response: "StartResponse" + ) -> t.Iterable[bytes]: + headers = list(EnvironHeaders(environ).items()) + headers[:] = [ + (k, v) + for k, v in headers + if not is_hop_by_hop_header(k) + and k.lower() not in ("content-length", "host") + ] + headers.append(("Connection", "close")) + + if opts["host"] == "": + headers.append(("Host", host)) + elif opts["host"] is None: + headers.append(("Host", environ["HTTP_HOST"])) + else: + headers.append(("Host", opts["host"])) + + headers.extend(opts["headers"].items()) + remote_path = path + + if opts["remove_prefix"]: + remote_path = remote_path[len(prefix) :].lstrip("/") + remote_path = f"{target.path.rstrip('/')}/{remote_path}" + + content_length = environ.get("CONTENT_LENGTH") + chunked = False + + if content_length not in ("", None): + headers.append(("Content-Length", content_length)) # type: ignore + elif content_length is not None: + headers.append(("Transfer-Encoding", "chunked")) + chunked = True + + try: + if target.scheme == "http": + con = client.HTTPConnection( + host, target.port or 80, timeout=self.timeout + ) + elif target.scheme == "https": + con = client.HTTPSConnection( + host, + target.port or 443, + timeout=self.timeout, + context=opts["ssl_context"], + ) + else: + raise RuntimeError( + "Target scheme must be 'http' or 'https', got" + f" {target.scheme!r}." + ) + + con.connect() + remote_url = url_quote(remote_path) + querystring = environ["QUERY_STRING"] + + if querystring: + remote_url = f"{remote_url}?{querystring}" + + con.putrequest(environ["REQUEST_METHOD"], remote_url, skip_host=True) + + for k, v in headers: + if k.lower() == "connection": + v = "close" + + con.putheader(k, v) + + con.endheaders() + stream = get_input_stream(environ) + + while True: + data = stream.read(self.chunk_size) + + if not data: + break + + if chunked: + con.send(b"%x\r\n%s\r\n" % (len(data), data)) + else: + con.send(data) + + resp = con.getresponse() + except OSError: + from ..exceptions import BadGateway + + return BadGateway()(environ, start_response) + + start_response( + f"{resp.status} {resp.reason}", + [ + (k.title(), v) + for k, v in resp.getheaders() + if not is_hop_by_hop_header(k) + ], + ) + + def read() -> t.Iterator[bytes]: + while True: + try: + data = resp.read(self.chunk_size) + except OSError: + break + + if not data: + break + + yield data + + return read() + + return application + + def __call__( + self, environ: "WSGIEnvironment", start_response: "StartResponse" + ) -> t.Iterable[bytes]: + path = environ["PATH_INFO"] + app = self.app + + for prefix, opts in self.targets.items(): + if path.startswith(prefix): + app = self.proxy_to(opts, path, prefix) + break + + return app(environ, start_response) diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/middleware/lint.py b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/middleware/lint.py new file mode 100644 index 000000000..80c423dd3 --- /dev/null +++ b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/middleware/lint.py @@ -0,0 +1,420 @@ +""" +WSGI Protocol Linter +==================== + +This module provides a middleware that performs sanity checks on the +behavior of the WSGI server and application. It checks that the +:pep:`3333` WSGI spec is properly implemented. It also warns on some +common HTTP errors such as non-empty responses for 304 status codes. + +.. autoclass:: LintMiddleware + +:copyright: 2007 Pallets +:license: BSD-3-Clause +""" +import typing as t +from types import TracebackType +from urllib.parse import urlparse +from warnings import warn + +from ..datastructures import Headers +from ..http import is_entity_header +from ..wsgi import FileWrapper + +if t.TYPE_CHECKING: + from _typeshed.wsgi import StartResponse + from _typeshed.wsgi import WSGIApplication + from _typeshed.wsgi import WSGIEnvironment + + +class WSGIWarning(Warning): + """Warning class for WSGI warnings.""" + + +class HTTPWarning(Warning): + """Warning class for HTTP warnings.""" + + +def check_type(context: str, obj: object, need: t.Type = str) -> None: + if type(obj) is not need: + warn( + f"{context!r} requires {need.__name__!r}, got {type(obj).__name__!r}.", + WSGIWarning, + stacklevel=3, + ) + + +class InputStream: + def __init__(self, stream: t.BinaryIO) -> None: + self._stream = stream + + def read(self, *args: t.Any) -> bytes: + if len(args) == 0: + warn( + "WSGI does not guarantee an EOF marker on the input stream, thus making" + " calls to 'wsgi.input.read()' unsafe. Conforming servers may never" + " return from this call.", + WSGIWarning, + stacklevel=2, + ) + elif len(args) != 1: + warn( + "Too many parameters passed to 'wsgi.input.read()'.", + WSGIWarning, + stacklevel=2, + ) + return self._stream.read(*args) + + def readline(self, *args: t.Any) -> bytes: + if len(args) == 0: + warn( + "Calls to 'wsgi.input.readline()' without arguments are unsafe. Use" + " 'wsgi.input.read()' instead.", + WSGIWarning, + stacklevel=2, + ) + elif len(args) == 1: + warn( + "'wsgi.input.readline()' was called with a size hint. WSGI does not" + " support this, although it's available on all major servers.", + WSGIWarning, + stacklevel=2, + ) + else: + raise TypeError("Too many arguments passed to 'wsgi.input.readline()'.") + return self._stream.readline(*args) + + def __iter__(self) -> t.Iterator[bytes]: + try: + return iter(self._stream) + except TypeError: + warn("'wsgi.input' is not iterable.", WSGIWarning, stacklevel=2) + return iter(()) + + def close(self) -> None: + warn("The application closed the input stream!", WSGIWarning, stacklevel=2) + self._stream.close() + + +class ErrorStream: + def __init__(self, stream: t.TextIO) -> None: + self._stream = stream + + def write(self, s: str) -> None: + check_type("wsgi.error.write()", s, str) + self._stream.write(s) + + def flush(self) -> None: + self._stream.flush() + + def writelines(self, seq: t.Iterable[str]) -> None: + for line in seq: + self.write(line) + + def close(self) -> None: + warn("The application closed the error stream!", WSGIWarning, stacklevel=2) + self._stream.close() + + +class GuardedWrite: + def __init__(self, write: t.Callable[[bytes], None], chunks: t.List[int]) -> None: + self._write = write + self._chunks = chunks + + def __call__(self, s: bytes) -> None: + check_type("write()", s, bytes) + self._write(s) + self._chunks.append(len(s)) + + +class GuardedIterator: + def __init__( + self, + iterator: t.Iterable[bytes], + headers_set: t.Tuple[int, Headers], + chunks: t.List[int], + ) -> None: + self._iterator = iterator + self._next = iter(iterator).__next__ + self.closed = False + self.headers_set = headers_set + self.chunks = chunks + + def __iter__(self) -> "GuardedIterator": + return self + + def __next__(self) -> bytes: + if self.closed: + warn("Iterated over closed 'app_iter'.", WSGIWarning, stacklevel=2) + + rv = self._next() + + if not self.headers_set: + warn( + "The application returned before it started the response.", + WSGIWarning, + stacklevel=2, + ) + + check_type("application iterator items", rv, bytes) + self.chunks.append(len(rv)) + return rv + + def close(self) -> None: + self.closed = True + + if hasattr(self._iterator, "close"): + self._iterator.close() # type: ignore + + if self.headers_set: + status_code, headers = self.headers_set + bytes_sent = sum(self.chunks) + content_length = headers.get("content-length", type=int) + + if status_code == 304: + for key, _value in headers: + key = key.lower() + if key not in ("expires", "content-location") and is_entity_header( + key + ): + warn( + f"Entity header {key!r} found in 304 response.", HTTPWarning + ) + if bytes_sent: + warn("304 responses must not have a body.", HTTPWarning) + elif 100 <= status_code < 200 or status_code == 204: + if content_length != 0: + warn( + f"{status_code} responses must have an empty content length.", + HTTPWarning, + ) + if bytes_sent: + warn(f"{status_code} responses must not have a body.", HTTPWarning) + elif content_length is not None and content_length != bytes_sent: + warn( + "Content-Length and the number of bytes sent to the" + " client do not match.", + WSGIWarning, + ) + + def __del__(self) -> None: + if not self.closed: + try: + warn( + "Iterator was garbage collected before it was closed.", WSGIWarning + ) + except Exception: + pass + + +class LintMiddleware: + """Warns about common errors in the WSGI and HTTP behavior of the + server and wrapped application. Some of the issues it checks are: + + - invalid status codes + - non-bytes sent to the WSGI server + - strings returned from the WSGI application + - non-empty conditional responses + - unquoted etags + - relative URLs in the Location header + - unsafe calls to wsgi.input + - unclosed iterators + + Error information is emitted using the :mod:`warnings` module. + + :param app: The WSGI application to wrap. + + .. code-block:: python + + from werkzeug.middleware.lint import LintMiddleware + app = LintMiddleware(app) + """ + + def __init__(self, app: "WSGIApplication") -> None: + self.app = app + + def check_environ(self, environ: "WSGIEnvironment") -> None: + if type(environ) is not dict: + warn( + "WSGI environment is not a standard Python dict.", + WSGIWarning, + stacklevel=4, + ) + for key in ( + "REQUEST_METHOD", + "SERVER_NAME", + "SERVER_PORT", + "wsgi.version", + "wsgi.input", + "wsgi.errors", + "wsgi.multithread", + "wsgi.multiprocess", + "wsgi.run_once", + ): + if key not in environ: + warn( + f"Required environment key {key!r} not found", + WSGIWarning, + stacklevel=3, + ) + if environ["wsgi.version"] != (1, 0): + warn("Environ is not a WSGI 1.0 environ.", WSGIWarning, stacklevel=3) + + script_name = environ.get("SCRIPT_NAME", "") + path_info = environ.get("PATH_INFO", "") + + if script_name and script_name[0] != "/": + warn( + f"'SCRIPT_NAME' does not start with a slash: {script_name!r}", + WSGIWarning, + stacklevel=3, + ) + + if path_info and path_info[0] != "/": + warn( + f"'PATH_INFO' does not start with a slash: {path_info!r}", + WSGIWarning, + stacklevel=3, + ) + + def check_start_response( + self, + status: str, + headers: t.List[t.Tuple[str, str]], + exc_info: t.Optional[ + t.Tuple[t.Type[BaseException], BaseException, TracebackType] + ], + ) -> t.Tuple[int, Headers]: + check_type("status", status, str) + status_code_str = status.split(None, 1)[0] + + if len(status_code_str) != 3 or not status_code_str.isdigit(): + warn("Status code must be three digits.", WSGIWarning, stacklevel=3) + + if len(status) < 4 or status[3] != " ": + warn( + f"Invalid value for status {status!r}. Valid status strings are three" + " digits, a space and a status explanation.", + WSGIWarning, + stacklevel=3, + ) + + status_code = int(status_code_str) + + if status_code < 100: + warn("Status code < 100 detected.", WSGIWarning, stacklevel=3) + + if type(headers) is not list: + warn("Header list is not a list.", WSGIWarning, stacklevel=3) + + for item in headers: + if type(item) is not tuple or len(item) != 2: + warn("Header items must be 2-item tuples.", WSGIWarning, stacklevel=3) + name, value = item + if type(name) is not str or type(value) is not str: + warn( + "Header keys and values must be strings.", WSGIWarning, stacklevel=3 + ) + if name.lower() == "status": + warn( + "The status header is not supported due to" + " conflicts with the CGI spec.", + WSGIWarning, + stacklevel=3, + ) + + if exc_info is not None and not isinstance(exc_info, tuple): + warn("Invalid value for exc_info.", WSGIWarning, stacklevel=3) + + headers = Headers(headers) + self.check_headers(headers) + + return status_code, headers + + def check_headers(self, headers: Headers) -> None: + etag = headers.get("etag") + + if etag is not None: + if etag.startswith(("W/", "w/")): + if etag.startswith("w/"): + warn( + "Weak etag indicator should be upper case.", + HTTPWarning, + stacklevel=4, + ) + + etag = etag[2:] + + if not (etag[:1] == etag[-1:] == '"'): + warn("Unquoted etag emitted.", HTTPWarning, stacklevel=4) + + location = headers.get("location") + + if location is not None: + if not urlparse(location).netloc: + warn( + "Absolute URLs required for location header.", + HTTPWarning, + stacklevel=4, + ) + + def check_iterator(self, app_iter: t.Iterable[bytes]) -> None: + if isinstance(app_iter, bytes): + warn( + "The application returned a bytestring. The response will send one" + " character at a time to the client, which will kill performance." + " Return a list or iterable instead.", + WSGIWarning, + stacklevel=3, + ) + + def __call__(self, *args: t.Any, **kwargs: t.Any) -> t.Iterable[bytes]: + if len(args) != 2: + warn("A WSGI app takes two arguments.", WSGIWarning, stacklevel=2) + + if kwargs: + warn( + "A WSGI app does not take keyword arguments.", WSGIWarning, stacklevel=2 + ) + + environ: "WSGIEnvironment" = args[0] + start_response: "StartResponse" = args[1] + + self.check_environ(environ) + environ["wsgi.input"] = InputStream(environ["wsgi.input"]) + environ["wsgi.errors"] = ErrorStream(environ["wsgi.errors"]) + + # Hook our own file wrapper in so that applications will always + # iterate to the end and we can check the content length. + environ["wsgi.file_wrapper"] = FileWrapper + + headers_set: t.List[t.Any] = [] + chunks: t.List[int] = [] + + def checking_start_response( + *args: t.Any, **kwargs: t.Any + ) -> t.Callable[[bytes], None]: + if len(args) not in {2, 3}: + warn( + f"Invalid number of arguments: {len(args)}, expected 2 or 3.", + WSGIWarning, + stacklevel=2, + ) + + if kwargs: + warn("'start_response' does not take keyword arguments.", WSGIWarning) + + status: str = args[0] + headers: t.List[t.Tuple[str, str]] = args[1] + exc_info: t.Optional[ + t.Tuple[t.Type[BaseException], BaseException, TracebackType] + ] = (args[2] if len(args) == 3 else None) + + headers_set[:] = self.check_start_response(status, headers, exc_info) + return GuardedWrite(start_response(status, headers, exc_info), chunks) + + app_iter = self.app(environ, t.cast("StartResponse", checking_start_response)) + self.check_iterator(app_iter) + return GuardedIterator( + app_iter, t.cast(t.Tuple[int, Headers], headers_set), chunks + ) diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/middleware/profiler.py b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/middleware/profiler.py new file mode 100644 index 000000000..0992f8f16 --- /dev/null +++ b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/middleware/profiler.py @@ -0,0 +1,139 @@ +""" +Application Profiler +==================== + +This module provides a middleware that profiles each request with the +:mod:`cProfile` module. This can help identify bottlenecks in your code +that may be slowing down your application. + +.. autoclass:: ProfilerMiddleware + +:copyright: 2007 Pallets +:license: BSD-3-Clause +""" +import os.path +import sys +import time +import typing as t +from pstats import Stats + +try: + from cProfile import Profile +except ImportError: + from profile import Profile # type: ignore + +if t.TYPE_CHECKING: + from _typeshed.wsgi import StartResponse + from _typeshed.wsgi import WSGIApplication + from _typeshed.wsgi import WSGIEnvironment + + +class ProfilerMiddleware: + """Wrap a WSGI application and profile the execution of each + request. Responses are buffered so that timings are more exact. + + If ``stream`` is given, :class:`pstats.Stats` are written to it + after each request. If ``profile_dir`` is given, :mod:`cProfile` + data files are saved to that directory, one file per request. + + The filename can be customized by passing ``filename_format``. If + it is a string, it will be formatted using :meth:`str.format` with + the following fields available: + + - ``{method}`` - The request method; GET, POST, etc. + - ``{path}`` - The request path or 'root' should one not exist. + - ``{elapsed}`` - The elapsed time of the request. + - ``{time}`` - The time of the request. + + If it is a callable, it will be called with the WSGI ``environ`` + dict and should return a filename. + + :param app: The WSGI application to wrap. + :param stream: Write stats to this stream. Disable with ``None``. + :param sort_by: A tuple of columns to sort stats by. See + :meth:`pstats.Stats.sort_stats`. + :param restrictions: A tuple of restrictions to filter stats by. See + :meth:`pstats.Stats.print_stats`. + :param profile_dir: Save profile data files to this directory. + :param filename_format: Format string for profile data file names, + or a callable returning a name. See explanation above. + + .. code-block:: python + + from werkzeug.middleware.profiler import ProfilerMiddleware + app = ProfilerMiddleware(app) + + .. versionchanged:: 0.15 + Stats are written even if ``profile_dir`` is given, and can be + disable by passing ``stream=None``. + + .. versionadded:: 0.15 + Added ``filename_format``. + + .. versionadded:: 0.9 + Added ``restrictions`` and ``profile_dir``. + """ + + def __init__( + self, + app: "WSGIApplication", + stream: t.TextIO = sys.stdout, + sort_by: t.Iterable[str] = ("time", "calls"), + restrictions: t.Iterable[t.Union[str, int, float]] = (), + profile_dir: t.Optional[str] = None, + filename_format: str = "{method}.{path}.{elapsed:.0f}ms.{time:.0f}.prof", + ) -> None: + self._app = app + self._stream = stream + self._sort_by = sort_by + self._restrictions = restrictions + self._profile_dir = profile_dir + self._filename_format = filename_format + + def __call__( + self, environ: "WSGIEnvironment", start_response: "StartResponse" + ) -> t.Iterable[bytes]: + response_body: t.List[bytes] = [] + + def catching_start_response(status, headers, exc_info=None): # type: ignore + start_response(status, headers, exc_info) + return response_body.append + + def runapp() -> None: + app_iter = self._app( + environ, t.cast("StartResponse", catching_start_response) + ) + response_body.extend(app_iter) + + if hasattr(app_iter, "close"): + app_iter.close() # type: ignore + + profile = Profile() + start = time.time() + profile.runcall(runapp) + body = b"".join(response_body) + elapsed = time.time() - start + + if self._profile_dir is not None: + if callable(self._filename_format): + filename = self._filename_format(environ) + else: + filename = self._filename_format.format( + method=environ["REQUEST_METHOD"], + path=environ["PATH_INFO"].strip("/").replace("/", ".") or "root", + elapsed=elapsed * 1000.0, + time=time.time(), + ) + filename = os.path.join(self._profile_dir, filename) + profile.dump_stats(filename) + + if self._stream is not None: + stats = Stats(profile, stream=self._stream) + stats.sort_stats(*self._sort_by) + print("-" * 80, file=self._stream) + path_info = environ.get("PATH_INFO", "") + print(f"PATH: {path_info!r}", file=self._stream) + stats.print_stats(*self._restrictions) + print(f"{'-' * 80}\n", file=self._stream) + + return [body] diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/middleware/proxy_fix.py b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/middleware/proxy_fix.py new file mode 100644 index 000000000..e90b1b34e --- /dev/null +++ b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/middleware/proxy_fix.py @@ -0,0 +1,187 @@ +""" +X-Forwarded-For Proxy Fix +========================= + +This module provides a middleware that adjusts the WSGI environ based on +``X-Forwarded-`` headers that proxies in front of an application may +set. + +When an application is running behind a proxy server, WSGI may see the +request as coming from that server rather than the real client. Proxies +set various headers to track where the request actually came from. + +This middleware should only be used if the application is actually +behind such a proxy, and should be configured with the number of proxies +that are chained in front of it. Not all proxies set all the headers. +Since incoming headers can be faked, you must set how many proxies are +setting each header so the middleware knows what to trust. + +.. autoclass:: ProxyFix + +:copyright: 2007 Pallets +:license: BSD-3-Clause +""" +import typing as t + +from ..http import parse_list_header + +if t.TYPE_CHECKING: + from _typeshed.wsgi import StartResponse + from _typeshed.wsgi import WSGIApplication + from _typeshed.wsgi import WSGIEnvironment + + +class ProxyFix: + """Adjust the WSGI environ based on ``X-Forwarded-`` that proxies in + front of the application may set. + + - ``X-Forwarded-For`` sets ``REMOTE_ADDR``. + - ``X-Forwarded-Proto`` sets ``wsgi.url_scheme``. + - ``X-Forwarded-Host`` sets ``HTTP_HOST``, ``SERVER_NAME``, and + ``SERVER_PORT``. + - ``X-Forwarded-Port`` sets ``HTTP_HOST`` and ``SERVER_PORT``. + - ``X-Forwarded-Prefix`` sets ``SCRIPT_NAME``. + + You must tell the middleware how many proxies set each header so it + knows what values to trust. It is a security issue to trust values + that came from the client rather than a proxy. + + The original values of the headers are stored in the WSGI + environ as ``werkzeug.proxy_fix.orig``, a dict. + + :param app: The WSGI application to wrap. + :param x_for: Number of values to trust for ``X-Forwarded-For``. + :param x_proto: Number of values to trust for ``X-Forwarded-Proto``. + :param x_host: Number of values to trust for ``X-Forwarded-Host``. + :param x_port: Number of values to trust for ``X-Forwarded-Port``. + :param x_prefix: Number of values to trust for + ``X-Forwarded-Prefix``. + + .. code-block:: python + + from werkzeug.middleware.proxy_fix import ProxyFix + # App is behind one proxy that sets the -For and -Host headers. + app = ProxyFix(app, x_for=1, x_host=1) + + .. versionchanged:: 1.0 + Deprecated code has been removed: + + * The ``num_proxies`` argument and attribute. + * The ``get_remote_addr`` method. + * The environ keys ``orig_remote_addr``, + ``orig_wsgi_url_scheme``, and ``orig_http_host``. + + .. versionchanged:: 0.15 + All headers support multiple values. The ``num_proxies`` + argument is deprecated. Each header is configured with a + separate number of trusted proxies. + + .. versionchanged:: 0.15 + Original WSGI environ values are stored in the + ``werkzeug.proxy_fix.orig`` dict. ``orig_remote_addr``, + ``orig_wsgi_url_scheme``, and ``orig_http_host`` are deprecated + and will be removed in 1.0. + + .. versionchanged:: 0.15 + Support ``X-Forwarded-Port`` and ``X-Forwarded-Prefix``. + + .. versionchanged:: 0.15 + ``X-Forwarded-Host`` and ``X-Forwarded-Port`` modify + ``SERVER_NAME`` and ``SERVER_PORT``. + """ + + def __init__( + self, + app: "WSGIApplication", + x_for: int = 1, + x_proto: int = 1, + x_host: int = 0, + x_port: int = 0, + x_prefix: int = 0, + ) -> None: + self.app = app + self.x_for = x_for + self.x_proto = x_proto + self.x_host = x_host + self.x_port = x_port + self.x_prefix = x_prefix + + def _get_real_value(self, trusted: int, value: t.Optional[str]) -> t.Optional[str]: + """Get the real value from a list header based on the configured + number of trusted proxies. + + :param trusted: Number of values to trust in the header. + :param value: Comma separated list header value to parse. + :return: The real value, or ``None`` if there are fewer values + than the number of trusted proxies. + + .. versionchanged:: 1.0 + Renamed from ``_get_trusted_comma``. + + .. versionadded:: 0.15 + """ + if not (trusted and value): + return None + values = parse_list_header(value) + if len(values) >= trusted: + return values[-trusted] + return None + + def __call__( + self, environ: "WSGIEnvironment", start_response: "StartResponse" + ) -> t.Iterable[bytes]: + """Modify the WSGI environ based on the various ``Forwarded`` + headers before calling the wrapped application. Store the + original environ values in ``werkzeug.proxy_fix.orig_{key}``. + """ + environ_get = environ.get + orig_remote_addr = environ_get("REMOTE_ADDR") + orig_wsgi_url_scheme = environ_get("wsgi.url_scheme") + orig_http_host = environ_get("HTTP_HOST") + environ.update( + { + "werkzeug.proxy_fix.orig": { + "REMOTE_ADDR": orig_remote_addr, + "wsgi.url_scheme": orig_wsgi_url_scheme, + "HTTP_HOST": orig_http_host, + "SERVER_NAME": environ_get("SERVER_NAME"), + "SERVER_PORT": environ_get("SERVER_PORT"), + "SCRIPT_NAME": environ_get("SCRIPT_NAME"), + } + } + ) + + x_for = self._get_real_value(self.x_for, environ_get("HTTP_X_FORWARDED_FOR")) + if x_for: + environ["REMOTE_ADDR"] = x_for + + x_proto = self._get_real_value( + self.x_proto, environ_get("HTTP_X_FORWARDED_PROTO") + ) + if x_proto: + environ["wsgi.url_scheme"] = x_proto + + x_host = self._get_real_value(self.x_host, environ_get("HTTP_X_FORWARDED_HOST")) + if x_host: + environ["HTTP_HOST"] = x_host + parts = x_host.split(":", 1) + environ["SERVER_NAME"] = parts[0] + if len(parts) == 2: + environ["SERVER_PORT"] = parts[1] + + x_port = self._get_real_value(self.x_port, environ_get("HTTP_X_FORWARDED_PORT")) + if x_port: + host = environ.get("HTTP_HOST") + if host: + parts = host.split(":", 1) + host = parts[0] if len(parts) == 2 else host + environ["HTTP_HOST"] = f"{host}:{x_port}" + environ["SERVER_PORT"] = x_port + + x_prefix = self._get_real_value( + self.x_prefix, environ_get("HTTP_X_FORWARDED_PREFIX") + ) + if x_prefix: + environ["SCRIPT_NAME"] = x_prefix + + return self.app(environ, start_response) diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/middleware/shared_data.py b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/middleware/shared_data.py new file mode 100644 index 000000000..f11b43a03 --- /dev/null +++ b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/middleware/shared_data.py @@ -0,0 +1,320 @@ +""" +Serve Shared Static Files +========================= + +.. autoclass:: SharedDataMiddleware + :members: is_allowed + +:copyright: 2007 Pallets +:license: BSD-3-Clause +""" +import mimetypes +import os +import pkgutil +import posixpath +import typing as t +from datetime import datetime +from datetime import timezone +from io import BytesIO +from time import time +from zlib import adler32 + +from ..filesystem import get_filesystem_encoding +from ..http import http_date +from ..http import is_resource_modified +from ..security import safe_join +from ..utils import get_content_type +from ..wsgi import get_path_info +from ..wsgi import wrap_file + +_TOpener = t.Callable[[], t.Tuple[t.BinaryIO, datetime, int]] +_TLoader = t.Callable[[t.Optional[str]], t.Tuple[t.Optional[str], t.Optional[_TOpener]]] + +if t.TYPE_CHECKING: + from _typeshed.wsgi import StartResponse + from _typeshed.wsgi import WSGIApplication + from _typeshed.wsgi import WSGIEnvironment + + +class SharedDataMiddleware: + + """A WSGI middleware which provides static content for development + environments or simple server setups. Its usage is quite simple:: + + import os + from werkzeug.middleware.shared_data import SharedDataMiddleware + + app = SharedDataMiddleware(app, { + '/shared': os.path.join(os.path.dirname(__file__), 'shared') + }) + + The contents of the folder ``./shared`` will now be available on + ``http://example.com/shared/``. This is pretty useful during development + because a standalone media server is not required. Files can also be + mounted on the root folder and still continue to use the application because + the shared data middleware forwards all unhandled requests to the + application, even if the requests are below one of the shared folders. + + If `pkg_resources` is available you can also tell the middleware to serve + files from package data:: + + app = SharedDataMiddleware(app, { + '/static': ('myapplication', 'static') + }) + + This will then serve the ``static`` folder in the `myapplication` + Python package. + + The optional `disallow` parameter can be a list of :func:`~fnmatch.fnmatch` + rules for files that are not accessible from the web. If `cache` is set to + `False` no caching headers are sent. + + Currently the middleware does not support non-ASCII filenames. If the + encoding on the file system happens to match the encoding of the URI it may + work but this could also be by accident. We strongly suggest using ASCII + only file names for static files. + + The middleware will guess the mimetype using the Python `mimetype` + module. If it's unable to figure out the charset it will fall back + to `fallback_mimetype`. + + :param app: the application to wrap. If you don't want to wrap an + application you can pass it :exc:`NotFound`. + :param exports: a list or dict of exported files and folders. + :param disallow: a list of :func:`~fnmatch.fnmatch` rules. + :param cache: enable or disable caching headers. + :param cache_timeout: the cache timeout in seconds for the headers. + :param fallback_mimetype: The fallback mimetype for unknown files. + + .. versionchanged:: 1.0 + The default ``fallback_mimetype`` is + ``application/octet-stream``. If a filename looks like a text + mimetype, the ``utf-8`` charset is added to it. + + .. versionadded:: 0.6 + Added ``fallback_mimetype``. + + .. versionchanged:: 0.5 + Added ``cache_timeout``. + """ + + def __init__( + self, + app: "WSGIApplication", + exports: t.Union[ + t.Dict[str, t.Union[str, t.Tuple[str, str]]], + t.Iterable[t.Tuple[str, t.Union[str, t.Tuple[str, str]]]], + ], + disallow: None = None, + cache: bool = True, + cache_timeout: int = 60 * 60 * 12, + fallback_mimetype: str = "application/octet-stream", + ) -> None: + self.app = app + self.exports: t.List[t.Tuple[str, _TLoader]] = [] + self.cache = cache + self.cache_timeout = cache_timeout + + if isinstance(exports, dict): + exports = exports.items() + + for key, value in exports: + if isinstance(value, tuple): + loader = self.get_package_loader(*value) + elif isinstance(value, str): + if os.path.isfile(value): + loader = self.get_file_loader(value) + else: + loader = self.get_directory_loader(value) + else: + raise TypeError(f"unknown def {value!r}") + + self.exports.append((key, loader)) + + if disallow is not None: + from fnmatch import fnmatch + + self.is_allowed = lambda x: not fnmatch(x, disallow) + + self.fallback_mimetype = fallback_mimetype + + def is_allowed(self, filename: str) -> bool: + """Subclasses can override this method to disallow the access to + certain files. However by providing `disallow` in the constructor + this method is overwritten. + """ + return True + + def _opener(self, filename: str) -> _TOpener: + return lambda: ( + open(filename, "rb"), + datetime.fromtimestamp(os.path.getmtime(filename), tz=timezone.utc), + int(os.path.getsize(filename)), + ) + + def get_file_loader(self, filename: str) -> _TLoader: + return lambda x: (os.path.basename(filename), self._opener(filename)) + + def get_package_loader(self, package: str, package_path: str) -> _TLoader: + load_time = datetime.now(timezone.utc) + provider = pkgutil.get_loader(package) + + if hasattr(provider, "get_resource_reader"): + # Python 3 + reader = provider.get_resource_reader(package) # type: ignore + + def loader( + path: t.Optional[str], + ) -> t.Tuple[t.Optional[str], t.Optional[_TOpener]]: + if path is None: + return None, None + + path = safe_join(package_path, path) + + if path is None: + return None, None + + basename = posixpath.basename(path) + + try: + resource = reader.open_resource(path) + except OSError: + return None, None + + if isinstance(resource, BytesIO): + return ( + basename, + lambda: (resource, load_time, len(resource.getvalue())), + ) + + return ( + basename, + lambda: ( + resource, + datetime.fromtimestamp( + os.path.getmtime(resource.name), tz=timezone.utc + ), + os.path.getsize(resource.name), + ), + ) + + else: + # Python 3.6 + package_filename = provider.get_filename(package) # type: ignore + is_filesystem = os.path.exists(package_filename) + root = os.path.join(os.path.dirname(package_filename), package_path) + + def loader( + path: t.Optional[str], + ) -> t.Tuple[t.Optional[str], t.Optional[_TOpener]]: + if path is None: + return None, None + + path = safe_join(root, path) + + if path is None: + return None, None + + basename = posixpath.basename(path) + + if is_filesystem: + if not os.path.isfile(path): + return None, None + + return basename, self._opener(path) + + try: + data = provider.get_data(path) # type: ignore + except OSError: + return None, None + + return basename, lambda: (BytesIO(data), load_time, len(data)) + + return loader + + def get_directory_loader(self, directory: str) -> _TLoader: + def loader( + path: t.Optional[str], + ) -> t.Tuple[t.Optional[str], t.Optional[_TOpener]]: + if path is not None: + path = safe_join(directory, path) + + if path is None: + return None, None + else: + path = directory + + if os.path.isfile(path): + return os.path.basename(path), self._opener(path) + + return None, None + + return loader + + def generate_etag(self, mtime: datetime, file_size: int, real_filename: str) -> str: + if not isinstance(real_filename, bytes): + real_filename = real_filename.encode( # type: ignore + get_filesystem_encoding() + ) + + timestamp = mtime.timestamp() + checksum = adler32(real_filename) & 0xFFFFFFFF # type: ignore + return f"wzsdm-{timestamp}-{file_size}-{checksum}" + + def __call__( + self, environ: "WSGIEnvironment", start_response: "StartResponse" + ) -> t.Iterable[bytes]: + path = get_path_info(environ) + file_loader = None + + for search_path, loader in self.exports: + if search_path == path: + real_filename, file_loader = loader(None) + + if file_loader is not None: + break + + if not search_path.endswith("/"): + search_path += "/" + + if path.startswith(search_path): + real_filename, file_loader = loader(path[len(search_path) :]) + + if file_loader is not None: + break + + if file_loader is None or not self.is_allowed(real_filename): # type: ignore + return self.app(environ, start_response) + + guessed_type = mimetypes.guess_type(real_filename) # type: ignore + mime_type = get_content_type(guessed_type[0] or self.fallback_mimetype, "utf-8") + f, mtime, file_size = file_loader() + + headers = [("Date", http_date())] + + if self.cache: + timeout = self.cache_timeout + etag = self.generate_etag(mtime, file_size, real_filename) # type: ignore + headers += [ + ("Etag", f'"{etag}"'), + ("Cache-Control", f"max-age={timeout}, public"), + ] + + if not is_resource_modified(environ, etag, last_modified=mtime): + f.close() + start_response("304 Not Modified", headers) + return [] + + headers.append(("Expires", http_date(time() + timeout))) + else: + headers.append(("Cache-Control", "public")) + + headers.extend( + ( + ("Content-Type", mime_type), + ("Content-Length", str(file_size)), + ("Last-Modified", http_date(mtime)), + ) + ) + start_response("200 OK", headers) + return wrap_file(environ, f) diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/py.typed b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/routing.py b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/routing.py new file mode 100644 index 000000000..104387593 --- /dev/null +++ b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/routing.py @@ -0,0 +1,2332 @@ +"""When it comes to combining multiple controller or view functions +(however you want to call them) you need a dispatcher. A simple way +would be applying regular expression tests on the ``PATH_INFO`` and +calling registered callback functions that return the value then. + +This module implements a much more powerful system than simple regular +expression matching because it can also convert values in the URLs and +build URLs. + +Here a simple example that creates a URL map for an application with +two subdomains (www and kb) and some URL rules: + +.. code-block:: python + + m = Map([ + # Static URLs + Rule('/', endpoint='static/index'), + Rule('/about', endpoint='static/about'), + Rule('/help', endpoint='static/help'), + # Knowledge Base + Subdomain('kb', [ + Rule('/', endpoint='kb/index'), + Rule('/browse/', endpoint='kb/browse'), + Rule('/browse//', endpoint='kb/browse'), + Rule('/browse//', endpoint='kb/browse') + ]) + ], default_subdomain='www') + +If the application doesn't use subdomains it's perfectly fine to not set +the default subdomain and not use the `Subdomain` rule factory. The +endpoint in the rules can be anything, for example import paths or +unique identifiers. The WSGI application can use those endpoints to get the +handler for that URL. It doesn't have to be a string at all but it's +recommended. + +Now it's possible to create a URL adapter for one of the subdomains and +build URLs: + +.. code-block:: python + + c = m.bind('example.com') + + c.build("kb/browse", dict(id=42)) + 'http://kb.example.com/browse/42/' + + c.build("kb/browse", dict()) + 'http://kb.example.com/browse/' + + c.build("kb/browse", dict(id=42, page=3)) + 'http://kb.example.com/browse/42/3' + + c.build("static/about") + '/about' + + c.build("static/index", force_external=True) + 'http://www.example.com/' + + c = m.bind('example.com', subdomain='kb') + + c.build("static/about") + 'http://www.example.com/about' + +The first argument to bind is the server name *without* the subdomain. +Per default it will assume that the script is mounted on the root, but +often that's not the case so you can provide the real mount point as +second argument: + +.. code-block:: python + + c = m.bind('example.com', '/applications/example') + +The third argument can be the subdomain, if not given the default +subdomain is used. For more details about binding have a look at the +documentation of the `MapAdapter`. + +And here is how you can match URLs: + +.. code-block:: python + + c = m.bind('example.com') + + c.match("/") + ('static/index', {}) + + c.match("/about") + ('static/about', {}) + + c = m.bind('example.com', '/', 'kb') + + c.match("/") + ('kb/index', {}) + + c.match("/browse/42/23") + ('kb/browse', {'id': 42, 'page': 23}) + +If matching fails you get a ``NotFound`` exception, if the rule thinks +it's a good idea to redirect (for example because the URL was defined +to have a slash at the end but the request was missing that slash) it +will raise a ``RequestRedirect`` exception. Both are subclasses of +``HTTPException`` so you can use those errors as responses in the +application. + +If matching succeeded but the URL rule was incompatible to the given +method (for example there were only rules for ``GET`` and ``HEAD`` but +routing tried to match a ``POST`` request) a ``MethodNotAllowed`` +exception is raised. +""" +import ast +import difflib +import posixpath +import re +import typing +import typing as t +import uuid +import warnings +from pprint import pformat +from string import Template +from threading import Lock +from types import CodeType + +from ._internal import _encode_idna +from ._internal import _get_environ +from ._internal import _to_bytes +from ._internal import _to_str +from ._internal import _wsgi_decoding_dance +from .datastructures import ImmutableDict +from .datastructures import MultiDict +from .exceptions import BadHost +from .exceptions import BadRequest +from .exceptions import HTTPException +from .exceptions import MethodNotAllowed +from .exceptions import NotFound +from .urls import _fast_url_quote +from .urls import url_encode +from .urls import url_join +from .urls import url_quote +from .utils import cached_property +from .utils import redirect +from .wsgi import get_host + +if t.TYPE_CHECKING: + import typing_extensions as te + from _typeshed.wsgi import WSGIApplication + from _typeshed.wsgi import WSGIEnvironment + from .wrappers.response import Response + +_rule_re = re.compile( + r""" + (?P[^<]*) # static rule data + < + (?: + (?P[a-zA-Z_][a-zA-Z0-9_]*) # converter name + (?:\((?P.*?)\))? # converter arguments + \: # variable delimiter + )? + (?P[a-zA-Z_][a-zA-Z0-9_]*) # variable name + > + """, + re.VERBOSE, +) +_simple_rule_re = re.compile(r"<([^>]+)>") +_converter_args_re = re.compile( + r""" + ((?P\w+)\s*=\s*)? + (?P + True|False| + \d+.\d+| + \d+.| + \d+| + [\w\d_.]+| + [urUR]?(?P"[^"]*?"|'[^']*') + )\s*, + """, + re.VERBOSE, +) + + +_PYTHON_CONSTANTS = {"None": None, "True": True, "False": False} + + +def _pythonize(value: str) -> t.Union[None, bool, int, float, str]: + if value in _PYTHON_CONSTANTS: + return _PYTHON_CONSTANTS[value] + for convert in int, float: + try: + return convert(value) # type: ignore + except ValueError: + pass + if value[:1] == value[-1:] and value[0] in "\"'": + value = value[1:-1] + return str(value) + + +def parse_converter_args(argstr: str) -> t.Tuple[t.Tuple, t.Dict[str, t.Any]]: + argstr += "," + args = [] + kwargs = {} + + for item in _converter_args_re.finditer(argstr): + value = item.group("stringval") + if value is None: + value = item.group("value") + value = _pythonize(value) + if not item.group("name"): + args.append(value) + else: + name = item.group("name") + kwargs[name] = value + + return tuple(args), kwargs + + +def parse_rule(rule: str) -> t.Iterator[t.Tuple[t.Optional[str], t.Optional[str], str]]: + """Parse a rule and return it as generator. Each iteration yields tuples + in the form ``(converter, arguments, variable)``. If the converter is + `None` it's a static url part, otherwise it's a dynamic one. + + :internal: + """ + pos = 0 + end = len(rule) + do_match = _rule_re.match + used_names = set() + while pos < end: + m = do_match(rule, pos) + if m is None: + break + data = m.groupdict() + if data["static"]: + yield None, None, data["static"] + variable = data["variable"] + converter = data["converter"] or "default" + if variable in used_names: + raise ValueError(f"variable name {variable!r} used twice.") + used_names.add(variable) + yield converter, data["args"] or None, variable + pos = m.end() + if pos < end: + remaining = rule[pos:] + if ">" in remaining or "<" in remaining: + raise ValueError(f"malformed url rule: {rule!r}") + yield None, None, remaining + + +class RoutingException(Exception): + """Special exceptions that require the application to redirect, notifying + about missing urls, etc. + + :internal: + """ + + +class RequestRedirect(HTTPException, RoutingException): + """Raise if the map requests a redirect. This is for example the case if + `strict_slashes` are activated and an url that requires a trailing slash. + + The attribute `new_url` contains the absolute destination url. + """ + + code = 308 + + def __init__(self, new_url: str) -> None: + super().__init__(new_url) + self.new_url = new_url + + def get_response( + self, + environ: t.Optional["WSGIEnvironment"] = None, + scope: t.Optional[dict] = None, + ) -> "Response": + return redirect(self.new_url, self.code) + + +class RequestPath(RoutingException): + """Internal exception.""" + + __slots__ = ("path_info",) + + def __init__(self, path_info: str) -> None: + super().__init__() + self.path_info = path_info + + +class RequestAliasRedirect(RoutingException): # noqa: B903 + """This rule is an alias and wants to redirect to the canonical URL.""" + + def __init__(self, matched_values: t.Mapping[str, t.Any]) -> None: + super().__init__() + self.matched_values = matched_values + + +class BuildError(RoutingException, LookupError): + """Raised if the build system cannot find a URL for an endpoint with the + values provided. + """ + + def __init__( + self, + endpoint: str, + values: t.Mapping[str, t.Any], + method: t.Optional[str], + adapter: t.Optional["MapAdapter"] = None, + ) -> None: + super().__init__(endpoint, values, method) + self.endpoint = endpoint + self.values = values + self.method = method + self.adapter = adapter + + @cached_property + def suggested(self) -> t.Optional["Rule"]: + return self.closest_rule(self.adapter) + + def closest_rule(self, adapter: t.Optional["MapAdapter"]) -> t.Optional["Rule"]: + def _score_rule(rule: "Rule") -> float: + return sum( + [ + 0.98 + * difflib.SequenceMatcher( + None, rule.endpoint, self.endpoint + ).ratio(), + 0.01 * bool(set(self.values or ()).issubset(rule.arguments)), + 0.01 * bool(rule.methods and self.method in rule.methods), + ] + ) + + if adapter and adapter.map._rules: + return max(adapter.map._rules, key=_score_rule) + + return None + + def __str__(self) -> str: + message = [f"Could not build url for endpoint {self.endpoint!r}"] + if self.method: + message.append(f" ({self.method!r})") + if self.values: + message.append(f" with values {sorted(self.values)!r}") + message.append(".") + if self.suggested: + if self.endpoint == self.suggested.endpoint: + if ( + self.method + and self.suggested.methods is not None + and self.method not in self.suggested.methods + ): + message.append( + " Did you mean to use methods" + f" {sorted(self.suggested.methods)!r}?" + ) + missing_values = self.suggested.arguments.union( + set(self.suggested.defaults or ()) + ) - set(self.values.keys()) + if missing_values: + message.append( + f" Did you forget to specify values {sorted(missing_values)!r}?" + ) + else: + message.append(f" Did you mean {self.suggested.endpoint!r} instead?") + return "".join(message) + + +class WebsocketMismatch(BadRequest): + """The only matched rule is either a WebSocket and the request is + HTTP, or the rule is HTTP and the request is a WebSocket. + """ + + +class ValidationError(ValueError): + """Validation error. If a rule converter raises this exception the rule + does not match the current URL and the next URL is tried. + """ + + +class RuleFactory: + """As soon as you have more complex URL setups it's a good idea to use rule + factories to avoid repetitive tasks. Some of them are builtin, others can + be added by subclassing `RuleFactory` and overriding `get_rules`. + """ + + def get_rules(self, map: "Map") -> t.Iterable["Rule"]: + """Subclasses of `RuleFactory` have to override this method and return + an iterable of rules.""" + raise NotImplementedError() + + +class Subdomain(RuleFactory): + """All URLs provided by this factory have the subdomain set to a + specific domain. For example if you want to use the subdomain for + the current language this can be a good setup:: + + url_map = Map([ + Rule('/', endpoint='#select_language'), + Subdomain('', [ + Rule('/', endpoint='index'), + Rule('/about', endpoint='about'), + Rule('/help', endpoint='help') + ]) + ]) + + All the rules except for the ``'#select_language'`` endpoint will now + listen on a two letter long subdomain that holds the language code + for the current request. + """ + + def __init__(self, subdomain: str, rules: t.Iterable["Rule"]) -> None: + self.subdomain = subdomain + self.rules = rules + + def get_rules(self, map: "Map") -> t.Iterator["Rule"]: + for rulefactory in self.rules: + for rule in rulefactory.get_rules(map): + rule = rule.empty() + rule.subdomain = self.subdomain + yield rule + + +class Submount(RuleFactory): + """Like `Subdomain` but prefixes the URL rule with a given string:: + + url_map = Map([ + Rule('/', endpoint='index'), + Submount('/blog', [ + Rule('/', endpoint='blog/index'), + Rule('/entry/', endpoint='blog/show') + ]) + ]) + + Now the rule ``'blog/show'`` matches ``/blog/entry/``. + """ + + def __init__(self, path: str, rules: t.Iterable["Rule"]) -> None: + self.path = path.rstrip("/") + self.rules = rules + + def get_rules(self, map: "Map") -> t.Iterator["Rule"]: + for rulefactory in self.rules: + for rule in rulefactory.get_rules(map): + rule = rule.empty() + rule.rule = self.path + rule.rule + yield rule + + +class EndpointPrefix(RuleFactory): + """Prefixes all endpoints (which must be strings for this factory) with + another string. This can be useful for sub applications:: + + url_map = Map([ + Rule('/', endpoint='index'), + EndpointPrefix('blog/', [Submount('/blog', [ + Rule('/', endpoint='index'), + Rule('/entry/', endpoint='show') + ])]) + ]) + """ + + def __init__(self, prefix: str, rules: t.Iterable["Rule"]) -> None: + self.prefix = prefix + self.rules = rules + + def get_rules(self, map: "Map") -> t.Iterator["Rule"]: + for rulefactory in self.rules: + for rule in rulefactory.get_rules(map): + rule = rule.empty() + rule.endpoint = self.prefix + rule.endpoint + yield rule + + +class RuleTemplate: + """Returns copies of the rules wrapped and expands string templates in + the endpoint, rule, defaults or subdomain sections. + + Here a small example for such a rule template:: + + from werkzeug.routing import Map, Rule, RuleTemplate + + resource = RuleTemplate([ + Rule('/$name/', endpoint='$name.list'), + Rule('/$name/', endpoint='$name.show') + ]) + + url_map = Map([resource(name='user'), resource(name='page')]) + + When a rule template is called the keyword arguments are used to + replace the placeholders in all the string parameters. + """ + + def __init__(self, rules: t.Iterable["Rule"]) -> None: + self.rules = list(rules) + + def __call__(self, *args: t.Any, **kwargs: t.Any) -> "RuleTemplateFactory": + return RuleTemplateFactory(self.rules, dict(*args, **kwargs)) + + +class RuleTemplateFactory(RuleFactory): + """A factory that fills in template variables into rules. Used by + `RuleTemplate` internally. + + :internal: + """ + + def __init__(self, rules: t.Iterable["Rule"], context: t.Dict[str, t.Any]) -> None: + self.rules = rules + self.context = context + + def get_rules(self, map: "Map") -> t.Iterator["Rule"]: + for rulefactory in self.rules: + for rule in rulefactory.get_rules(map): + new_defaults = subdomain = None + if rule.defaults: + new_defaults = {} + for key, value in rule.defaults.items(): + if isinstance(value, str): + value = Template(value).substitute(self.context) + new_defaults[key] = value + if rule.subdomain is not None: + subdomain = Template(rule.subdomain).substitute(self.context) + new_endpoint = rule.endpoint + if isinstance(new_endpoint, str): + new_endpoint = Template(new_endpoint).substitute(self.context) + yield Rule( + Template(rule.rule).substitute(self.context), + new_defaults, + subdomain, + rule.methods, + rule.build_only, + new_endpoint, + rule.strict_slashes, + ) + + +def _prefix_names(src: str) -> ast.stmt: + """ast parse and prefix names with `.` to avoid collision with user vars""" + tree = ast.parse(src).body[0] + if isinstance(tree, ast.Expr): + tree = tree.value # type: ignore + for node in ast.walk(tree): + if isinstance(node, ast.Name): + node.id = f".{node.id}" + return tree + + +_CALL_CONVERTER_CODE_FMT = "self._converters[{elem!r}].to_url()" +_IF_KWARGS_URL_ENCODE_CODE = """\ +if kwargs: + q = '?' + params = self._encode_query_vars(kwargs) +else: + q = params = '' +""" +_IF_KWARGS_URL_ENCODE_AST = _prefix_names(_IF_KWARGS_URL_ENCODE_CODE) +_URL_ENCODE_AST_NAMES = (_prefix_names("q"), _prefix_names("params")) + + +class Rule(RuleFactory): + """A Rule represents one URL pattern. There are some options for `Rule` + that change the way it behaves and are passed to the `Rule` constructor. + Note that besides the rule-string all arguments *must* be keyword arguments + in order to not break the application on Werkzeug upgrades. + + `string` + Rule strings basically are just normal URL paths with placeholders in + the format ```` where the converter and the + arguments are optional. If no converter is defined the `default` + converter is used which means `string` in the normal configuration. + + URL rules that end with a slash are branch URLs, others are leaves. + If you have `strict_slashes` enabled (which is the default), all + branch URLs that are matched without a trailing slash will trigger a + redirect to the same URL with the missing slash appended. + + The converters are defined on the `Map`. + + `endpoint` + The endpoint for this rule. This can be anything. A reference to a + function, a string, a number etc. The preferred way is using a string + because the endpoint is used for URL generation. + + `defaults` + An optional dict with defaults for other rules with the same endpoint. + This is a bit tricky but useful if you want to have unique URLs:: + + url_map = Map([ + Rule('/all/', defaults={'page': 1}, endpoint='all_entries'), + Rule('/all/page/', endpoint='all_entries') + ]) + + If a user now visits ``http://example.com/all/page/1`` he will be + redirected to ``http://example.com/all/``. If `redirect_defaults` is + disabled on the `Map` instance this will only affect the URL + generation. + + `subdomain` + The subdomain rule string for this rule. If not specified the rule + only matches for the `default_subdomain` of the map. If the map is + not bound to a subdomain this feature is disabled. + + Can be useful if you want to have user profiles on different subdomains + and all subdomains are forwarded to your application:: + + url_map = Map([ + Rule('/', subdomain='', endpoint='user/homepage'), + Rule('/stats', subdomain='', endpoint='user/stats') + ]) + + `methods` + A sequence of http methods this rule applies to. If not specified, all + methods are allowed. For example this can be useful if you want different + endpoints for `POST` and `GET`. If methods are defined and the path + matches but the method matched against is not in this list or in the + list of another rule for that path the error raised is of the type + `MethodNotAllowed` rather than `NotFound`. If `GET` is present in the + list of methods and `HEAD` is not, `HEAD` is added automatically. + + `strict_slashes` + Override the `Map` setting for `strict_slashes` only for this rule. If + not specified the `Map` setting is used. + + `merge_slashes` + Override :attr:`Map.merge_slashes` for this rule. + + `build_only` + Set this to True and the rule will never match but will create a URL + that can be build. This is useful if you have resources on a subdomain + or folder that are not handled by the WSGI application (like static data) + + `redirect_to` + If given this must be either a string or callable. In case of a + callable it's called with the url adapter that triggered the match and + the values of the URL as keyword arguments and has to return the target + for the redirect, otherwise it has to be a string with placeholders in + rule syntax:: + + def foo_with_slug(adapter, id): + # ask the database for the slug for the old id. this of + # course has nothing to do with werkzeug. + return f'foo/{Foo.get_slug_for_id(id)}' + + url_map = Map([ + Rule('/foo/', endpoint='foo'), + Rule('/some/old/url/', redirect_to='foo/'), + Rule('/other/old/url/', redirect_to=foo_with_slug) + ]) + + When the rule is matched the routing system will raise a + `RequestRedirect` exception with the target for the redirect. + + Keep in mind that the URL will be joined against the URL root of the + script so don't use a leading slash on the target URL unless you + really mean root of that domain. + + `alias` + If enabled this rule serves as an alias for another rule with the same + endpoint and arguments. + + `host` + If provided and the URL map has host matching enabled this can be + used to provide a match rule for the whole host. This also means + that the subdomain feature is disabled. + + `websocket` + If ``True``, this rule is only matches for WebSocket (``ws://``, + ``wss://``) requests. By default, rules will only match for HTTP + requests. + + .. versionadded:: 1.0 + Added ``websocket``. + + .. versionadded:: 1.0 + Added ``merge_slashes``. + + .. versionadded:: 0.7 + Added ``alias`` and ``host``. + + .. versionchanged:: 0.6.1 + ``HEAD`` is added to ``methods`` if ``GET`` is present. + """ + + def __init__( + self, + string: str, + defaults: t.Optional[t.Mapping[str, t.Any]] = None, + subdomain: t.Optional[str] = None, + methods: t.Optional[t.Iterable[str]] = None, + build_only: bool = False, + endpoint: t.Optional[str] = None, + strict_slashes: t.Optional[bool] = None, + merge_slashes: t.Optional[bool] = None, + redirect_to: t.Optional[t.Union[str, t.Callable[..., str]]] = None, + alias: bool = False, + host: t.Optional[str] = None, + websocket: bool = False, + ) -> None: + if not string.startswith("/"): + raise ValueError("urls must start with a leading slash") + self.rule = string + self.is_leaf = not string.endswith("/") + + self.map: "Map" = None # type: ignore + self.strict_slashes = strict_slashes + self.merge_slashes = merge_slashes + self.subdomain = subdomain + self.host = host + self.defaults = defaults + self.build_only = build_only + self.alias = alias + self.websocket = websocket + + if methods is not None: + if isinstance(methods, str): + raise TypeError("'methods' should be a list of strings.") + + methods = {x.upper() for x in methods} + + if "HEAD" not in methods and "GET" in methods: + methods.add("HEAD") + + if websocket and methods - {"GET", "HEAD", "OPTIONS"}: + raise ValueError( + "WebSocket rules can only use 'GET', 'HEAD', and 'OPTIONS' methods." + ) + + self.methods = methods + self.endpoint: str = endpoint # type: ignore + self.redirect_to = redirect_to + + if defaults: + self.arguments = set(map(str, defaults)) + else: + self.arguments = set() + + self._trace: t.List[t.Tuple[bool, str]] = [] + + def empty(self) -> "Rule": + """ + Return an unbound copy of this rule. + + This can be useful if want to reuse an already bound URL for another + map. See ``get_empty_kwargs`` to override what keyword arguments are + provided to the new copy. + """ + return type(self)(self.rule, **self.get_empty_kwargs()) + + def get_empty_kwargs(self) -> t.Mapping[str, t.Any]: + """ + Provides kwargs for instantiating empty copy with empty() + + Use this method to provide custom keyword arguments to the subclass of + ``Rule`` when calling ``some_rule.empty()``. Helpful when the subclass + has custom keyword arguments that are needed at instantiation. + + Must return a ``dict`` that will be provided as kwargs to the new + instance of ``Rule``, following the initial ``self.rule`` value which + is always provided as the first, required positional argument. + """ + defaults = None + if self.defaults: + defaults = dict(self.defaults) + return dict( + defaults=defaults, + subdomain=self.subdomain, + methods=self.methods, + build_only=self.build_only, + endpoint=self.endpoint, + strict_slashes=self.strict_slashes, + redirect_to=self.redirect_to, + alias=self.alias, + host=self.host, + ) + + def get_rules(self, map: "Map") -> t.Iterator["Rule"]: + yield self + + def refresh(self) -> None: + """Rebinds and refreshes the URL. Call this if you modified the + rule in place. + + :internal: + """ + self.bind(self.map, rebind=True) + + def bind(self, map: "Map", rebind: bool = False) -> None: + """Bind the url to a map and create a regular expression based on + the information from the rule itself and the defaults from the map. + + :internal: + """ + if self.map is not None and not rebind: + raise RuntimeError(f"url rule {self!r} already bound to map {self.map!r}") + self.map = map + if self.strict_slashes is None: + self.strict_slashes = map.strict_slashes + if self.merge_slashes is None: + self.merge_slashes = map.merge_slashes + if self.subdomain is None: + self.subdomain = map.default_subdomain + self.compile() + + def get_converter( + self, + variable_name: str, + converter_name: str, + args: t.Tuple, + kwargs: t.Mapping[str, t.Any], + ) -> "BaseConverter": + """Looks up the converter for the given parameter. + + .. versionadded:: 0.9 + """ + if converter_name not in self.map.converters: + raise LookupError(f"the converter {converter_name!r} does not exist") + return self.map.converters[converter_name](self.map, *args, **kwargs) + + def _encode_query_vars(self, query_vars: t.Mapping[str, t.Any]) -> str: + return url_encode( + query_vars, + charset=self.map.charset, + sort=self.map.sort_parameters, + key=self.map.sort_key, + ) + + def compile(self) -> None: + """Compiles the regular expression and stores it.""" + assert self.map is not None, "rule not bound" + + if self.map.host_matching: + domain_rule = self.host or "" + else: + domain_rule = self.subdomain or "" + + self._trace = [] + self._converters: t.Dict[str, "BaseConverter"] = {} + self._static_weights: t.List[t.Tuple[int, int]] = [] + self._argument_weights: t.List[int] = [] + regex_parts = [] + + def _build_regex(rule: str) -> None: + index = 0 + for converter, arguments, variable in parse_rule(rule): + if converter is None: + for match in re.finditer(r"/+|[^/]+", variable): + part = match.group(0) + if part.startswith("/"): + if self.merge_slashes: + regex_parts.append(r"/+?") + self._trace.append((False, "/")) + else: + regex_parts.append(part) + self._trace.append((False, part)) + continue + self._trace.append((False, part)) + regex_parts.append(re.escape(part)) + if part: + self._static_weights.append((index, -len(part))) + else: + if arguments: + c_args, c_kwargs = parse_converter_args(arguments) + else: + c_args = () + c_kwargs = {} + convobj = self.get_converter(variable, converter, c_args, c_kwargs) + regex_parts.append(f"(?P<{variable}>{convobj.regex})") + self._converters[variable] = convobj + self._trace.append((True, variable)) + self._argument_weights.append(convobj.weight) + self.arguments.add(str(variable)) + index = index + 1 + + _build_regex(domain_rule) + regex_parts.append("\\|") + self._trace.append((False, "|")) + _build_regex(self.rule if self.is_leaf else self.rule.rstrip("/")) + if not self.is_leaf: + self._trace.append((False, "/")) + + self._build: t.Callable[..., t.Tuple[str, str]] + self._build = self._compile_builder(False).__get__(self, None) # type: ignore + self._build_unknown: t.Callable[..., t.Tuple[str, str]] + self._build_unknown = self._compile_builder(True).__get__( # type: ignore + self, None + ) + + if self.build_only: + return + + if not (self.is_leaf and self.strict_slashes): + reps = "*" if self.merge_slashes else "?" + tail = f"(?/{reps})" + else: + tail = "" + + regex = f"^{''.join(regex_parts)}{tail}$" + self._regex = re.compile(regex) + + def match( + self, path: str, method: t.Optional[str] = None + ) -> t.Optional[t.MutableMapping[str, t.Any]]: + """Check if the rule matches a given path. Path is a string in the + form ``"subdomain|/path"`` and is assembled by the map. If + the map is doing host matching the subdomain part will be the host + instead. + + If the rule matches a dict with the converted values is returned, + otherwise the return value is `None`. + + :internal: + """ + if not self.build_only: + require_redirect = False + + m = self._regex.search(path) + if m is not None: + groups = m.groupdict() + # we have a folder like part of the url without a trailing + # slash and strict slashes enabled. raise an exception that + # tells the map to redirect to the same url but with a + # trailing slash + if ( + self.strict_slashes + and not self.is_leaf + and not groups.pop("__suffix__") + and ( + method is None or self.methods is None or method in self.methods + ) + ): + path += "/" + require_redirect = True + # if we are not in strict slashes mode we have to remove + # a __suffix__ + elif not self.strict_slashes: + del groups["__suffix__"] + + result = {} + for name, value in groups.items(): + try: + value = self._converters[name].to_python(value) + except ValidationError: + return None + result[str(name)] = value + if self.defaults: + result.update(self.defaults) + + if self.merge_slashes: + new_path = "|".join(self.build(result, False)) # type: ignore + if path.endswith("/") and not new_path.endswith("/"): + new_path += "/" + if new_path.count("/") < path.count("/"): + path = new_path + require_redirect = True + + if require_redirect: + path = path.split("|", 1)[1] + raise RequestPath(path) + + if self.alias and self.map.redirect_defaults: + raise RequestAliasRedirect(result) + + return result + + return None + + @staticmethod + def _get_func_code(code: CodeType, name: str) -> t.Callable[..., t.Tuple[str, str]]: + globs: t.Dict[str, t.Any] = {} + locs: t.Dict[str, t.Any] = {} + exec(code, globs, locs) + return locs[name] # type: ignore + + def _compile_builder( + self, append_unknown: bool = True + ) -> t.Callable[..., t.Tuple[str, str]]: + defaults = self.defaults or {} + dom_ops: t.List[t.Tuple[bool, str]] = [] + url_ops: t.List[t.Tuple[bool, str]] = [] + + opl = dom_ops + for is_dynamic, data in self._trace: + if data == "|" and opl is dom_ops: + opl = url_ops + continue + # this seems like a silly case to ever come up but: + # if a default is given for a value that appears in the rule, + # resolve it to a constant ahead of time + if is_dynamic and data in defaults: + data = self._converters[data].to_url(defaults[data]) + opl.append((False, data)) + elif not is_dynamic: + opl.append( + (False, url_quote(_to_bytes(data, self.map.charset), safe="/:|+")) + ) + else: + opl.append((True, data)) + + def _convert(elem: str) -> ast.stmt: + ret = _prefix_names(_CALL_CONVERTER_CODE_FMT.format(elem=elem)) + ret.args = [ast.Name(str(elem), ast.Load())] # type: ignore # str for py2 + return ret + + def _parts(ops: t.List[t.Tuple[bool, str]]) -> t.List[ast.AST]: + parts = [ + _convert(elem) if is_dynamic else ast.Str(s=elem) + for is_dynamic, elem in ops + ] + parts = parts or [ast.Str("")] + # constant fold + ret = [parts[0]] + for p in parts[1:]: + if isinstance(p, ast.Str) and isinstance(ret[-1], ast.Str): + ret[-1] = ast.Str(ret[-1].s + p.s) + else: + ret.append(p) + return ret + + dom_parts = _parts(dom_ops) + url_parts = _parts(url_ops) + if not append_unknown: + body = [] + else: + body = [_IF_KWARGS_URL_ENCODE_AST] + url_parts.extend(_URL_ENCODE_AST_NAMES) + + def _join(parts: t.List[ast.AST]) -> ast.AST: + if len(parts) == 1: # shortcut + return parts[0] + return ast.JoinedStr(parts) + + body.append( + ast.Return(ast.Tuple([_join(dom_parts), _join(url_parts)], ast.Load())) + ) + + pargs = [ + elem + for is_dynamic, elem in dom_ops + url_ops + if is_dynamic and elem not in defaults + ] + kargs = [str(k) for k in defaults] + + func_ast: ast.FunctionDef = _prefix_names("def _(): pass") # type: ignore + func_ast.name = f"" + func_ast.args.args.append(ast.arg(".self", None)) + for arg in pargs + kargs: + func_ast.args.args.append(ast.arg(arg, None)) + func_ast.args.kwarg = ast.arg(".kwargs", None) + for _ in kargs: + func_ast.args.defaults.append(ast.Str("")) + func_ast.body = body + + # use `ast.parse` instead of `ast.Module` for better portability + # Python 3.8 changes the signature of `ast.Module` + module = ast.parse("") + module.body = [func_ast] + + # mark everything as on line 1, offset 0 + # less error-prone than `ast.fix_missing_locations` + # bad line numbers cause an assert to fail in debug builds + for node in ast.walk(module): + if "lineno" in node._attributes: + node.lineno = 1 + if "col_offset" in node._attributes: + node.col_offset = 0 + + code = compile(module, "", "exec") + return self._get_func_code(code, func_ast.name) + + def build( + self, values: t.Mapping[str, t.Any], append_unknown: bool = True + ) -> t.Optional[t.Tuple[str, str]]: + """Assembles the relative url for that rule and the subdomain. + If building doesn't work for some reasons `None` is returned. + + :internal: + """ + try: + if append_unknown: + return self._build_unknown(**values) + else: + return self._build(**values) + except ValidationError: + return None + + def provides_defaults_for(self, rule: "Rule") -> bool: + """Check if this rule has defaults for a given rule. + + :internal: + """ + return bool( + not self.build_only + and self.defaults + and self.endpoint == rule.endpoint + and self != rule + and self.arguments == rule.arguments + ) + + def suitable_for( + self, values: t.Mapping[str, t.Any], method: t.Optional[str] = None + ) -> bool: + """Check if the dict of values has enough data for url generation. + + :internal: + """ + # if a method was given explicitly and that method is not supported + # by this rule, this rule is not suitable. + if ( + method is not None + and self.methods is not None + and method not in self.methods + ): + return False + + defaults = self.defaults or () + + # all arguments required must be either in the defaults dict or + # the value dictionary otherwise it's not suitable + for key in self.arguments: + if key not in defaults and key not in values: + return False + + # in case defaults are given we ensure that either the value was + # skipped or the value is the same as the default value. + if defaults: + for key, value in defaults.items(): + if key in values and value != values[key]: + return False + + return True + + def match_compare_key( + self, + ) -> t.Tuple[bool, int, t.Iterable[t.Tuple[int, int]], int, t.Iterable[int]]: + """The match compare key for sorting. + + Current implementation: + + 1. rules without any arguments come first for performance + reasons only as we expect them to match faster and some + common ones usually don't have any arguments (index pages etc.) + 2. rules with more static parts come first so the second argument + is the negative length of the number of the static weights. + 3. we order by static weights, which is a combination of index + and length + 4. The more complex rules come first so the next argument is the + negative length of the number of argument weights. + 5. lastly we order by the actual argument weights. + + :internal: + """ + return ( + bool(self.arguments), + -len(self._static_weights), + self._static_weights, + -len(self._argument_weights), + self._argument_weights, + ) + + def build_compare_key(self) -> t.Tuple[int, int, int]: + """The build compare key for sorting. + + :internal: + """ + return (1 if self.alias else 0, -len(self.arguments), -len(self.defaults or ())) + + def __eq__(self, other: object) -> bool: + return isinstance(other, type(self)) and self._trace == other._trace + + __hash__ = None # type: ignore + + def __str__(self) -> str: + return self.rule + + def __repr__(self) -> str: + if self.map is None: + return f"<{type(self).__name__} (unbound)>" + parts = [] + for is_dynamic, data in self._trace: + if is_dynamic: + parts.append(f"<{data}>") + else: + parts.append(data) + parts = "".join(parts).lstrip("|") + methods = f" ({', '.join(self.methods)})" if self.methods is not None else "" + return f"<{type(self).__name__} {parts!r}{methods} -> {self.endpoint}>" + + +class BaseConverter: + """Base class for all converters.""" + + regex = "[^/]+" + weight = 100 + + def __init__(self, map: "Map", *args: t.Any, **kwargs: t.Any) -> None: + self.map = map + + def to_python(self, value: str) -> t.Any: + return value + + def to_url(self, value: t.Any) -> str: + if isinstance(value, (bytes, bytearray)): + return _fast_url_quote(value) + return _fast_url_quote(str(value).encode(self.map.charset)) + + +class UnicodeConverter(BaseConverter): + """This converter is the default converter and accepts any string but + only one path segment. Thus the string can not include a slash. + + This is the default validator. + + Example:: + + Rule('/pages/'), + Rule('/') + + :param map: the :class:`Map`. + :param minlength: the minimum length of the string. Must be greater + or equal 1. + :param maxlength: the maximum length of the string. + :param length: the exact length of the string. + """ + + def __init__( + self, + map: "Map", + minlength: int = 1, + maxlength: t.Optional[int] = None, + length: t.Optional[int] = None, + ) -> None: + super().__init__(map) + if length is not None: + length_regex = f"{{{int(length)}}}" + else: + if maxlength is None: + maxlength_value = "" + else: + maxlength_value = str(int(maxlength)) + length_regex = f"{{{int(minlength)},{maxlength_value}}}" + self.regex = f"[^/]{length_regex}" + + +class AnyConverter(BaseConverter): + """Matches one of the items provided. Items can either be Python + identifiers or strings:: + + Rule('/') + + :param map: the :class:`Map`. + :param items: this function accepts the possible items as positional + arguments. + """ + + def __init__(self, map: "Map", *items: str) -> None: + super().__init__(map) + self.regex = f"(?:{'|'.join([re.escape(x) for x in items])})" + + +class PathConverter(BaseConverter): + """Like the default :class:`UnicodeConverter`, but it also matches + slashes. This is useful for wikis and similar applications:: + + Rule('/') + Rule('//edit') + + :param map: the :class:`Map`. + """ + + regex = "[^/].*?" + weight = 200 + + +class NumberConverter(BaseConverter): + """Baseclass for `IntegerConverter` and `FloatConverter`. + + :internal: + """ + + weight = 50 + num_convert: t.Callable = int + + def __init__( + self, + map: "Map", + fixed_digits: int = 0, + min: t.Optional[int] = None, + max: t.Optional[int] = None, + signed: bool = False, + ) -> None: + if signed: + self.regex = self.signed_regex + super().__init__(map) + self.fixed_digits = fixed_digits + self.min = min + self.max = max + self.signed = signed + + def to_python(self, value: str) -> t.Any: + if self.fixed_digits and len(value) != self.fixed_digits: + raise ValidationError() + value = self.num_convert(value) + if (self.min is not None and value < self.min) or ( + self.max is not None and value > self.max + ): + raise ValidationError() + return value + + def to_url(self, value: t.Any) -> str: + value = str(self.num_convert(value)) + if self.fixed_digits: + value = value.zfill(self.fixed_digits) + return value + + @property + def signed_regex(self) -> str: + return f"-?{self.regex}" + + +class IntegerConverter(NumberConverter): + """This converter only accepts integer values:: + + Rule("/page/") + + By default it only accepts unsigned, positive values. The ``signed`` + parameter will enable signed, negative values. :: + + Rule("/page/") + + :param map: The :class:`Map`. + :param fixed_digits: The number of fixed digits in the URL. If you + set this to ``4`` for example, the rule will only match if the + URL looks like ``/0001/``. The default is variable length. + :param min: The minimal value. + :param max: The maximal value. + :param signed: Allow signed (negative) values. + + .. versionadded:: 0.15 + The ``signed`` parameter. + """ + + regex = r"\d+" + + +class FloatConverter(NumberConverter): + """This converter only accepts floating point values:: + + Rule("/probability/") + + By default it only accepts unsigned, positive values. The ``signed`` + parameter will enable signed, negative values. :: + + Rule("/offset/") + + :param map: The :class:`Map`. + :param min: The minimal value. + :param max: The maximal value. + :param signed: Allow signed (negative) values. + + .. versionadded:: 0.15 + The ``signed`` parameter. + """ + + regex = r"\d+\.\d+" + num_convert = float + + def __init__( + self, + map: "Map", + min: t.Optional[float] = None, + max: t.Optional[float] = None, + signed: bool = False, + ) -> None: + super().__init__(map, min=min, max=max, signed=signed) # type: ignore + + +class UUIDConverter(BaseConverter): + """This converter only accepts UUID strings:: + + Rule('/object/') + + .. versionadded:: 0.10 + + :param map: the :class:`Map`. + """ + + regex = ( + r"[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-" + r"[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}" + ) + + def to_python(self, value: str) -> uuid.UUID: + return uuid.UUID(value) + + def to_url(self, value: uuid.UUID) -> str: + return str(value) + + +#: the default converter mapping for the map. +DEFAULT_CONVERTERS: t.Mapping[str, t.Type[BaseConverter]] = { + "default": UnicodeConverter, + "string": UnicodeConverter, + "any": AnyConverter, + "path": PathConverter, + "int": IntegerConverter, + "float": FloatConverter, + "uuid": UUIDConverter, +} + + +class Map: + """The map class stores all the URL rules and some configuration + parameters. Some of the configuration values are only stored on the + `Map` instance since those affect all rules, others are just defaults + and can be overridden for each rule. Note that you have to specify all + arguments besides the `rules` as keyword arguments! + + :param rules: sequence of url rules for this map. + :param default_subdomain: The default subdomain for rules without a + subdomain defined. + :param charset: charset of the url. defaults to ``"utf-8"`` + :param strict_slashes: If a rule ends with a slash but the matched + URL does not, redirect to the URL with a trailing slash. + :param merge_slashes: Merge consecutive slashes when matching or + building URLs. Matches will redirect to the normalized URL. + Slashes in variable parts are not merged. + :param redirect_defaults: This will redirect to the default rule if it + wasn't visited that way. This helps creating + unique URLs. + :param converters: A dict of converters that adds additional converters + to the list of converters. If you redefine one + converter this will override the original one. + :param sort_parameters: If set to `True` the url parameters are sorted. + See `url_encode` for more details. + :param sort_key: The sort key function for `url_encode`. + :param encoding_errors: the error method to use for decoding + :param host_matching: if set to `True` it enables the host matching + feature and disables the subdomain one. If + enabled the `host` parameter to rules is used + instead of the `subdomain` one. + + .. versionchanged:: 1.0 + If ``url_scheme`` is ``ws`` or ``wss``, only WebSocket rules + will match. + + .. versionchanged:: 1.0 + Added ``merge_slashes``. + + .. versionchanged:: 0.7 + Added ``encoding_errors`` and ``host_matching``. + + .. versionchanged:: 0.5 + Added ``sort_parameters`` and ``sort_key``. + """ + + #: A dict of default converters to be used. + default_converters = ImmutableDict(DEFAULT_CONVERTERS) + + #: The type of lock to use when updating. + #: + #: .. versionadded:: 1.0 + lock_class = Lock + + def __init__( + self, + rules: t.Optional[t.Iterable[RuleFactory]] = None, + default_subdomain: str = "", + charset: str = "utf-8", + strict_slashes: bool = True, + merge_slashes: bool = True, + redirect_defaults: bool = True, + converters: t.Optional[t.Mapping[str, t.Type[BaseConverter]]] = None, + sort_parameters: bool = False, + sort_key: t.Optional[t.Callable[[t.Any], t.Any]] = None, + encoding_errors: str = "replace", + host_matching: bool = False, + ) -> None: + self._rules: t.List[Rule] = [] + self._rules_by_endpoint: t.Dict[str, t.List[Rule]] = {} + self._remap = True + self._remap_lock = self.lock_class() + + self.default_subdomain = default_subdomain + self.charset = charset + self.encoding_errors = encoding_errors + self.strict_slashes = strict_slashes + self.merge_slashes = merge_slashes + self.redirect_defaults = redirect_defaults + self.host_matching = host_matching + + self.converters = self.default_converters.copy() + if converters: + self.converters.update(converters) + + self.sort_parameters = sort_parameters + self.sort_key = sort_key + + for rulefactory in rules or (): + self.add(rulefactory) + + def is_endpoint_expecting(self, endpoint: str, *arguments: str) -> bool: + """Iterate over all rules and check if the endpoint expects + the arguments provided. This is for example useful if you have + some URLs that expect a language code and others that do not and + you want to wrap the builder a bit so that the current language + code is automatically added if not provided but endpoints expect + it. + + :param endpoint: the endpoint to check. + :param arguments: this function accepts one or more arguments + as positional arguments. Each one of them is + checked. + """ + self.update() + arguments = set(arguments) + for rule in self._rules_by_endpoint[endpoint]: + if arguments.issubset(rule.arguments): + return True + return False + + def iter_rules(self, endpoint: t.Optional[str] = None) -> t.Iterator[Rule]: + """Iterate over all rules or the rules of an endpoint. + + :param endpoint: if provided only the rules for that endpoint + are returned. + :return: an iterator + """ + self.update() + if endpoint is not None: + return iter(self._rules_by_endpoint[endpoint]) + return iter(self._rules) + + def add(self, rulefactory: RuleFactory) -> None: + """Add a new rule or factory to the map and bind it. Requires that the + rule is not bound to another map. + + :param rulefactory: a :class:`Rule` or :class:`RuleFactory` + """ + for rule in rulefactory.get_rules(self): + rule.bind(self) + self._rules.append(rule) + self._rules_by_endpoint.setdefault(rule.endpoint, []).append(rule) + self._remap = True + + def bind( + self, + server_name: str, + script_name: t.Optional[str] = None, + subdomain: t.Optional[str] = None, + url_scheme: str = "http", + default_method: str = "GET", + path_info: t.Optional[str] = None, + query_args: t.Optional[t.Union[t.Mapping[str, t.Any], str]] = None, + ) -> "MapAdapter": + """Return a new :class:`MapAdapter` with the details specified to the + call. Note that `script_name` will default to ``'/'`` if not further + specified or `None`. The `server_name` at least is a requirement + because the HTTP RFC requires absolute URLs for redirects and so all + redirect exceptions raised by Werkzeug will contain the full canonical + URL. + + If no path_info is passed to :meth:`match` it will use the default path + info passed to bind. While this doesn't really make sense for + manual bind calls, it's useful if you bind a map to a WSGI + environment which already contains the path info. + + `subdomain` will default to the `default_subdomain` for this map if + no defined. If there is no `default_subdomain` you cannot use the + subdomain feature. + + .. versionchanged:: 1.0 + If ``url_scheme`` is ``ws`` or ``wss``, only WebSocket rules + will match. + + .. versionchanged:: 0.15 + ``path_info`` defaults to ``'/'`` if ``None``. + + .. versionchanged:: 0.8 + ``query_args`` can be a string. + + .. versionchanged:: 0.7 + Added ``query_args``. + """ + server_name = server_name.lower() + if self.host_matching: + if subdomain is not None: + raise RuntimeError("host matching enabled and a subdomain was provided") + elif subdomain is None: + subdomain = self.default_subdomain + if script_name is None: + script_name = "/" + if path_info is None: + path_info = "/" + try: + server_name = _encode_idna(server_name) # type: ignore + except UnicodeError: + raise BadHost() + return MapAdapter( + self, + server_name, + script_name, + subdomain, + url_scheme, + path_info, + default_method, + query_args, + ) + + def bind_to_environ( + self, + environ: "WSGIEnvironment", + server_name: t.Optional[str] = None, + subdomain: t.Optional[str] = None, + ) -> "MapAdapter": + """Like :meth:`bind` but you can pass it an WSGI environment and it + will fetch the information from that dictionary. Note that because of + limitations in the protocol there is no way to get the current + subdomain and real `server_name` from the environment. If you don't + provide it, Werkzeug will use `SERVER_NAME` and `SERVER_PORT` (or + `HTTP_HOST` if provided) as used `server_name` with disabled subdomain + feature. + + If `subdomain` is `None` but an environment and a server name is + provided it will calculate the current subdomain automatically. + Example: `server_name` is ``'example.com'`` and the `SERVER_NAME` + in the wsgi `environ` is ``'staging.dev.example.com'`` the calculated + subdomain will be ``'staging.dev'``. + + If the object passed as environ has an environ attribute, the value of + this attribute is used instead. This allows you to pass request + objects. Additionally `PATH_INFO` added as a default of the + :class:`MapAdapter` so that you don't have to pass the path info to + the match method. + + .. versionchanged:: 1.0.0 + If the passed server name specifies port 443, it will match + if the incoming scheme is ``https`` without a port. + + .. versionchanged:: 1.0.0 + A warning is shown when the passed server name does not + match the incoming WSGI server name. + + .. versionchanged:: 0.8 + This will no longer raise a ValueError when an unexpected server + name was passed. + + .. versionchanged:: 0.5 + previously this method accepted a bogus `calculate_subdomain` + parameter that did not have any effect. It was removed because + of that. + + :param environ: a WSGI environment. + :param server_name: an optional server name hint (see above). + :param subdomain: optionally the current subdomain (see above). + """ + environ = _get_environ(environ) + wsgi_server_name = get_host(environ).lower() + scheme = environ["wsgi.url_scheme"] + + if ( + environ.get("HTTP_CONNECTION", "").lower() == "upgrade" + and environ.get("HTTP_UPGRADE", "").lower() == "websocket" + ): + scheme = "wss" if scheme == "https" else "ws" + + if server_name is None: + server_name = wsgi_server_name + else: + server_name = server_name.lower() + + # strip standard port to match get_host() + if scheme in {"http", "ws"} and server_name.endswith(":80"): + server_name = server_name[:-3] + elif scheme in {"https", "wss"} and server_name.endswith(":443"): + server_name = server_name[:-4] + + if subdomain is None and not self.host_matching: + cur_server_name = wsgi_server_name.split(".") + real_server_name = server_name.split(".") + offset = -len(real_server_name) + + if cur_server_name[offset:] != real_server_name: + # This can happen even with valid configs if the server was + # accessed directly by IP address under some situations. + # Instead of raising an exception like in Werkzeug 0.7 or + # earlier we go by an invalid subdomain which will result + # in a 404 error on matching. + warnings.warn( + f"Current server name {wsgi_server_name!r} doesn't match configured" + f" server name {server_name!r}", + stacklevel=2, + ) + subdomain = "" + else: + subdomain = ".".join(filter(None, cur_server_name[:offset])) + + def _get_wsgi_string(name: str) -> t.Optional[str]: + val = environ.get(name) + if val is not None: + return _wsgi_decoding_dance(val, self.charset) + return None + + script_name = _get_wsgi_string("SCRIPT_NAME") + path_info = _get_wsgi_string("PATH_INFO") + query_args = _get_wsgi_string("QUERY_STRING") + return Map.bind( + self, + server_name, + script_name, + subdomain, + scheme, + environ["REQUEST_METHOD"], + path_info, + query_args=query_args, + ) + + def update(self) -> None: + """Called before matching and building to keep the compiled rules + in the correct order after things changed. + """ + if not self._remap: + return + + with self._remap_lock: + if not self._remap: + return + + self._rules.sort(key=lambda x: x.match_compare_key()) + for rules in self._rules_by_endpoint.values(): + rules.sort(key=lambda x: x.build_compare_key()) + self._remap = False + + def __repr__(self) -> str: + rules = self.iter_rules() + return f"{type(self).__name__}({pformat(list(rules))})" + + +class MapAdapter: + + """Returned by :meth:`Map.bind` or :meth:`Map.bind_to_environ` and does + the URL matching and building based on runtime information. + """ + + def __init__( + self, + map: Map, + server_name: str, + script_name: str, + subdomain: t.Optional[str], + url_scheme: str, + path_info: str, + default_method: str, + query_args: t.Optional[t.Union[t.Mapping[str, t.Any], str]] = None, + ): + self.map = map + self.server_name = _to_str(server_name) + script_name = _to_str(script_name) + if not script_name.endswith("/"): + script_name += "/" + self.script_name = script_name + self.subdomain = _to_str(subdomain) + self.url_scheme = _to_str(url_scheme) + self.path_info = _to_str(path_info) + self.default_method = _to_str(default_method) + self.query_args = query_args + self.websocket = self.url_scheme in {"ws", "wss"} + + def dispatch( + self, + view_func: t.Callable[[str, t.Mapping[str, t.Any]], "WSGIApplication"], + path_info: t.Optional[str] = None, + method: t.Optional[str] = None, + catch_http_exceptions: bool = False, + ) -> "WSGIApplication": + """Does the complete dispatching process. `view_func` is called with + the endpoint and a dict with the values for the view. It should + look up the view function, call it, and return a response object + or WSGI application. http exceptions are not caught by default + so that applications can display nicer error messages by just + catching them by hand. If you want to stick with the default + error messages you can pass it ``catch_http_exceptions=True`` and + it will catch the http exceptions. + + Here a small example for the dispatch usage:: + + from werkzeug.wrappers import Request, Response + from werkzeug.wsgi import responder + from werkzeug.routing import Map, Rule + + def on_index(request): + return Response('Hello from the index') + + url_map = Map([Rule('/', endpoint='index')]) + views = {'index': on_index} + + @responder + def application(environ, start_response): + request = Request(environ) + urls = url_map.bind_to_environ(environ) + return urls.dispatch(lambda e, v: views[e](request, **v), + catch_http_exceptions=True) + + Keep in mind that this method might return exception objects, too, so + use :class:`Response.force_type` to get a response object. + + :param view_func: a function that is called with the endpoint as + first argument and the value dict as second. Has + to dispatch to the actual view function with this + information. (see above) + :param path_info: the path info to use for matching. Overrides the + path info specified on binding. + :param method: the HTTP method used for matching. Overrides the + method specified on binding. + :param catch_http_exceptions: set to `True` to catch any of the + werkzeug :class:`HTTPException`\\s. + """ + try: + try: + endpoint, args = self.match(path_info, method) + except RequestRedirect as e: + return e + return view_func(endpoint, args) + except HTTPException as e: + if catch_http_exceptions: + return e + raise + + @typing.overload + def match( # type: ignore + self, + path_info: t.Optional[str] = None, + method: t.Optional[str] = None, + return_rule: "te.Literal[False]" = False, + query_args: t.Optional[t.Union[t.Mapping[str, t.Any], str]] = None, + websocket: t.Optional[bool] = None, + ) -> t.Tuple[str, t.Mapping[str, t.Any]]: + ... + + @typing.overload + def match( + self, + path_info: t.Optional[str] = None, + method: t.Optional[str] = None, + return_rule: "te.Literal[True]" = True, + query_args: t.Optional[t.Union[t.Mapping[str, t.Any], str]] = None, + websocket: t.Optional[bool] = None, + ) -> t.Tuple[Rule, t.Mapping[str, t.Any]]: + ... + + def match( + self, + path_info: t.Optional[str] = None, + method: t.Optional[str] = None, + return_rule: bool = False, + query_args: t.Optional[t.Union[t.Mapping[str, t.Any], str]] = None, + websocket: t.Optional[bool] = None, + ) -> t.Tuple[t.Union[str, Rule], t.Mapping[str, t.Any]]: + """The usage is simple: you just pass the match method the current + path info as well as the method (which defaults to `GET`). The + following things can then happen: + + - you receive a `NotFound` exception that indicates that no URL is + matching. A `NotFound` exception is also a WSGI application you + can call to get a default page not found page (happens to be the + same object as `werkzeug.exceptions.NotFound`) + + - you receive a `MethodNotAllowed` exception that indicates that there + is a match for this URL but not for the current request method. + This is useful for RESTful applications. + + - you receive a `RequestRedirect` exception with a `new_url` + attribute. This exception is used to notify you about a request + Werkzeug requests from your WSGI application. This is for example the + case if you request ``/foo`` although the correct URL is ``/foo/`` + You can use the `RequestRedirect` instance as response-like object + similar to all other subclasses of `HTTPException`. + + - you receive a ``WebsocketMismatch`` exception if the only + match is a WebSocket rule but the bind is an HTTP request, or + if the match is an HTTP rule but the bind is a WebSocket + request. + + - you get a tuple in the form ``(endpoint, arguments)`` if there is + a match (unless `return_rule` is True, in which case you get a tuple + in the form ``(rule, arguments)``) + + If the path info is not passed to the match method the default path + info of the map is used (defaults to the root URL if not defined + explicitly). + + All of the exceptions raised are subclasses of `HTTPException` so they + can be used as WSGI responses. They will all render generic error or + redirect pages. + + Here is a small example for matching: + + >>> m = Map([ + ... Rule('/', endpoint='index'), + ... Rule('/downloads/', endpoint='downloads/index'), + ... Rule('/downloads/', endpoint='downloads/show') + ... ]) + >>> urls = m.bind("example.com", "/") + >>> urls.match("/", "GET") + ('index', {}) + >>> urls.match("/downloads/42") + ('downloads/show', {'id': 42}) + + And here is what happens on redirect and missing URLs: + + >>> urls.match("/downloads") + Traceback (most recent call last): + ... + RequestRedirect: http://example.com/downloads/ + >>> urls.match("/missing") + Traceback (most recent call last): + ... + NotFound: 404 Not Found + + :param path_info: the path info to use for matching. Overrides the + path info specified on binding. + :param method: the HTTP method used for matching. Overrides the + method specified on binding. + :param return_rule: return the rule that matched instead of just the + endpoint (defaults to `False`). + :param query_args: optional query arguments that are used for + automatic redirects as string or dictionary. It's + currently not possible to use the query arguments + for URL matching. + :param websocket: Match WebSocket instead of HTTP requests. A + websocket request has a ``ws`` or ``wss`` + :attr:`url_scheme`. This overrides that detection. + + .. versionadded:: 1.0 + Added ``websocket``. + + .. versionchanged:: 0.8 + ``query_args`` can be a string. + + .. versionadded:: 0.7 + Added ``query_args``. + + .. versionadded:: 0.6 + Added ``return_rule``. + """ + self.map.update() + if path_info is None: + path_info = self.path_info + else: + path_info = _to_str(path_info, self.map.charset) + if query_args is None: + query_args = self.query_args or {} + method = (method or self.default_method).upper() + + if websocket is None: + websocket = self.websocket + + require_redirect = False + + domain_part = self.server_name if self.map.host_matching else self.subdomain + path_part = f"/{path_info.lstrip('/')}" if path_info else "" + path = f"{domain_part}|{path_part}" + + have_match_for = set() + websocket_mismatch = False + + for rule in self.map._rules: + try: + rv = rule.match(path, method) + except RequestPath as e: + raise RequestRedirect( + self.make_redirect_url( + url_quote(e.path_info, self.map.charset, safe="/:|+"), + query_args, + ) + ) + except RequestAliasRedirect as e: + raise RequestRedirect( + self.make_alias_redirect_url( + path, rule.endpoint, e.matched_values, method, query_args + ) + ) + if rv is None: + continue + if rule.methods is not None and method not in rule.methods: + have_match_for.update(rule.methods) + continue + + if rule.websocket != websocket: + websocket_mismatch = True + continue + + if self.map.redirect_defaults: + redirect_url = self.get_default_redirect(rule, method, rv, query_args) + if redirect_url is not None: + raise RequestRedirect(redirect_url) + + if rule.redirect_to is not None: + if isinstance(rule.redirect_to, str): + + def _handle_match(match: t.Match[str]) -> str: + value = rv[match.group(1)] # type: ignore + return rule._converters[match.group(1)].to_url(value) + + redirect_url = _simple_rule_re.sub(_handle_match, rule.redirect_to) + else: + redirect_url = rule.redirect_to(self, **rv) + + if self.subdomain: + netloc = f"{self.subdomain}.{self.server_name}" + else: + netloc = self.server_name + + raise RequestRedirect( + url_join( + f"{self.url_scheme or 'http'}://{netloc}{self.script_name}", + redirect_url, + ) + ) + + if require_redirect: + raise RequestRedirect( + self.make_redirect_url( + url_quote(path_info, self.map.charset, safe="/:|+"), query_args + ) + ) + + if return_rule: + return rule, rv + else: + return rule.endpoint, rv + + if have_match_for: + raise MethodNotAllowed(valid_methods=list(have_match_for)) + + if websocket_mismatch: + raise WebsocketMismatch() + + raise NotFound() + + def test( + self, path_info: t.Optional[str] = None, method: t.Optional[str] = None + ) -> bool: + """Test if a rule would match. Works like `match` but returns `True` + if the URL matches, or `False` if it does not exist. + + :param path_info: the path info to use for matching. Overrides the + path info specified on binding. + :param method: the HTTP method used for matching. Overrides the + method specified on binding. + """ + try: + self.match(path_info, method) + except RequestRedirect: + pass + except HTTPException: + return False + return True + + def allowed_methods(self, path_info: t.Optional[str] = None) -> t.Iterable[str]: + """Returns the valid methods that match for a given path. + + .. versionadded:: 0.7 + """ + try: + self.match(path_info, method="--") + except MethodNotAllowed as e: + return e.valid_methods # type: ignore + except HTTPException: + pass + return [] + + def get_host(self, domain_part: t.Optional[str]) -> str: + """Figures out the full host name for the given domain part. The + domain part is a subdomain in case host matching is disabled or + a full host name. + """ + if self.map.host_matching: + if domain_part is None: + return self.server_name + return _to_str(domain_part, "ascii") + subdomain = domain_part + if subdomain is None: + subdomain = self.subdomain + else: + subdomain = _to_str(subdomain, "ascii") + + if subdomain: + return f"{subdomain}.{self.server_name}" + else: + return self.server_name + + def get_default_redirect( + self, + rule: Rule, + method: str, + values: t.MutableMapping[str, t.Any], + query_args: t.Union[t.Mapping[str, t.Any], str], + ) -> t.Optional[str]: + """A helper that returns the URL to redirect to if it finds one. + This is used for default redirecting only. + + :internal: + """ + assert self.map.redirect_defaults + for r in self.map._rules_by_endpoint[rule.endpoint]: + # every rule that comes after this one, including ourself + # has a lower priority for the defaults. We order the ones + # with the highest priority up for building. + if r is rule: + break + if r.provides_defaults_for(rule) and r.suitable_for(values, method): + values.update(r.defaults) # type: ignore + domain_part, path = r.build(values) # type: ignore + return self.make_redirect_url(path, query_args, domain_part=domain_part) + return None + + def encode_query_args(self, query_args: t.Union[t.Mapping[str, t.Any], str]) -> str: + if not isinstance(query_args, str): + return url_encode(query_args, self.map.charset) + return query_args + + def make_redirect_url( + self, + path_info: str, + query_args: t.Optional[t.Union[t.Mapping[str, t.Any], str]] = None, + domain_part: t.Optional[str] = None, + ) -> str: + """Creates a redirect URL. + + :internal: + """ + if query_args: + suffix = f"?{self.encode_query_args(query_args)}" + else: + suffix = "" + + scheme = self.url_scheme or "http" + host = self.get_host(domain_part) + path = posixpath.join(self.script_name.strip("/"), path_info.lstrip("/")) + return f"{scheme}://{host}/{path}{suffix}" + + def make_alias_redirect_url( + self, + path: str, + endpoint: str, + values: t.Mapping[str, t.Any], + method: str, + query_args: t.Union[t.Mapping[str, t.Any], str], + ) -> str: + """Internally called to make an alias redirect URL.""" + url = self.build( + endpoint, values, method, append_unknown=False, force_external=True + ) + if query_args: + url += f"?{self.encode_query_args(query_args)}" + assert url != path, "detected invalid alias setting. No canonical URL found" + return url + + def _partial_build( + self, + endpoint: str, + values: t.Mapping[str, t.Any], + method: t.Optional[str], + append_unknown: bool, + ) -> t.Optional[t.Tuple[str, str, bool]]: + """Helper for :meth:`build`. Returns subdomain and path for the + rule that accepts this endpoint, values and method. + + :internal: + """ + # in case the method is none, try with the default method first + if method is None: + rv = self._partial_build( + endpoint, values, self.default_method, append_unknown + ) + if rv is not None: + return rv + + # Default method did not match or a specific method is passed. + # Check all for first match with matching host. If no matching + # host is found, go with first result. + first_match = None + + for rule in self.map._rules_by_endpoint.get(endpoint, ()): + if rule.suitable_for(values, method): + build_rv = rule.build(values, append_unknown) + + if build_rv is not None: + rv = (build_rv[0], build_rv[1], rule.websocket) + if self.map.host_matching: + if rv[0] == self.server_name: + return rv + elif first_match is None: + first_match = rv + else: + return rv + + return first_match + + def build( + self, + endpoint: str, + values: t.Optional[t.Mapping[str, t.Any]] = None, + method: t.Optional[str] = None, + force_external: bool = False, + append_unknown: bool = True, + url_scheme: t.Optional[str] = None, + ) -> str: + """Building URLs works pretty much the other way round. Instead of + `match` you call `build` and pass it the endpoint and a dict of + arguments for the placeholders. + + The `build` function also accepts an argument called `force_external` + which, if you set it to `True` will force external URLs. Per default + external URLs (include the server name) will only be used if the + target URL is on a different subdomain. + + >>> m = Map([ + ... Rule('/', endpoint='index'), + ... Rule('/downloads/', endpoint='downloads/index'), + ... Rule('/downloads/', endpoint='downloads/show') + ... ]) + >>> urls = m.bind("example.com", "/") + >>> urls.build("index", {}) + '/' + >>> urls.build("downloads/show", {'id': 42}) + '/downloads/42' + >>> urls.build("downloads/show", {'id': 42}, force_external=True) + 'http://example.com/downloads/42' + + Because URLs cannot contain non ASCII data you will always get + bytes back. Non ASCII characters are urlencoded with the + charset defined on the map instance. + + Additional values are converted to strings and appended to the URL as + URL querystring parameters: + + >>> urls.build("index", {'q': 'My Searchstring'}) + '/?q=My+Searchstring' + + When processing those additional values, lists are furthermore + interpreted as multiple values (as per + :py:class:`werkzeug.datastructures.MultiDict`): + + >>> urls.build("index", {'q': ['a', 'b', 'c']}) + '/?q=a&q=b&q=c' + + Passing a ``MultiDict`` will also add multiple values: + + >>> urls.build("index", MultiDict((('p', 'z'), ('q', 'a'), ('q', 'b')))) + '/?p=z&q=a&q=b' + + If a rule does not exist when building a `BuildError` exception is + raised. + + The build method accepts an argument called `method` which allows you + to specify the method you want to have an URL built for if you have + different methods for the same endpoint specified. + + :param endpoint: the endpoint of the URL to build. + :param values: the values for the URL to build. Unhandled values are + appended to the URL as query parameters. + :param method: the HTTP method for the rule if there are different + URLs for different methods on the same endpoint. + :param force_external: enforce full canonical external URLs. If the URL + scheme is not provided, this will generate + a protocol-relative URL. + :param append_unknown: unknown parameters are appended to the generated + URL as query string argument. Disable this + if you want the builder to ignore those. + :param url_scheme: Scheme to use in place of the bound + :attr:`url_scheme`. + + .. versionchanged:: 2.0 + Added the ``url_scheme`` parameter. + + .. versionadded:: 0.6 + Added the ``append_unknown`` parameter. + """ + self.map.update() + + if values: + temp_values: t.Dict[str, t.Union[t.List[t.Any], t.Any]] = {} + always_list = isinstance(values, MultiDict) + key: str + value: t.Optional[t.Union[t.List[t.Any], t.Any]] + + # For MultiDict, dict.items(values) is like values.lists() + # without the call or list coercion overhead. + for key, value in dict.items(values): # type: ignore + if value is None: + continue + + if always_list or isinstance(value, (list, tuple)): + value = [v for v in value if v is not None] + + if not value: + continue + + if len(value) == 1: + value = value[0] + + temp_values[key] = value + + values = temp_values + else: + values = {} + + rv = self._partial_build(endpoint, values, method, append_unknown) + if rv is None: + raise BuildError(endpoint, values, method, self) + + domain_part, path, websocket = rv + host = self.get_host(domain_part) + + if url_scheme is None: + url_scheme = self.url_scheme + + # Always build WebSocket routes with the scheme (browsers + # require full URLs). If bound to a WebSocket, ensure that HTTP + # routes are built with an HTTP scheme. + secure = url_scheme in {"https", "wss"} + + if websocket: + force_external = True + url_scheme = "wss" if secure else "ws" + elif url_scheme: + url_scheme = "https" if secure else "http" + + # shortcut this. + if not force_external and ( + (self.map.host_matching and host == self.server_name) + or (not self.map.host_matching and domain_part == self.subdomain) + ): + return f"{self.script_name.rstrip('/')}/{path.lstrip('/')}" + + scheme = f"{url_scheme}:" if url_scheme else "" + return f"{scheme}//{host}{self.script_name[:-1]}/{path.lstrip('/')}" diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/sansio/__init__.py b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/sansio/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/sansio/__pycache__/__init__.cpython-38.pyc b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/sansio/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1359d98a6df88b1f600d8f90d725e1bb0568971d GIT binary patch literal 203 zcmWIL<>g`k0?Yf6i6Hthh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o5jtzo00yEU_e2 zzbvsxKP^8eCAFwn-!w_T!n`7*DBZ-=q&zF#EX6V_#W1fp%c3IJ00`3a^h5LuG7EG| z^9mA^vr|)a%QI3_b975g4K0(*Qp@zqQ;V{zQcKhIixcyTGxPQ1<1_OzOXB183My}L S*yQG?l;)(`fn4$#h#3HAOf_Zz literal 0 HcmV?d00001 diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/sansio/__pycache__/multipart.cpython-38.pyc b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/sansio/__pycache__/multipart.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..62f0fea38363d9a384f570001493ef1682933610 GIT binary patch literal 6558 zcma)ANpl;=6`mb~!9t3nNNOK#wvm`(oOqR&XoGFV5~Rpf7R2^sh;EVqfdSOhkVw|B zQZb!dT%~)QD)Q)aZaL>iBy-6jl~g&`RHag`IL`Na01{l}1gP$L{rYv!>$mqjD3v@7 zzt{f!({S#*ru~Hmqdyyit7!3-u4znTdRwd0(`f4*qi*N~n{BgW)hz{EfbF`iV7u*f za`l{o9l&ngRd5b)zMfaG3)riBfb-027dpjyQNabkrFu!h#df(fRv%MvsXg8~Qa_^L za(kk4w0>0A-qF|?8~;ROjGo2l zBGcd3D$jjEx~o<6G|xgAHrinvV_=1S*`;SS>UZeb2xCdl+fDU!@AhP~8-;D)?D>AL z&A%|P4@UVwc7ugA$!VJ$4Pq$>e3yre3&I||&mZ=AEUS@h%C-4!_jV|jI3^~0p@@0V zReQ(5vRcs~21kD|tIWWP0Jv7ynN~N{VT{zSKH`zAeQu_15JX{z2SMruL8r_5ZGwwI z@US1YvzgB|F^Mfy_vOR6o@hP_C7*i~in+yZo0y$@X<=^lmDOdj^x{h|K5i|&%wB7; z7oxcJ>T2gZ_$@_q^K-psZ?+%x!o~_`vyYcKZ_mn?UU+TcW&UXHF&8WAyuUOThf&<@ z&UN~2*~BU3<=&bY!^@AL#UwgcSL1nz!C4gDbsil!xCyeWL_dXYc<=`rH@U_1H{fB= zCk}p3@UXMc=BY&*t$l6_dWo2z?kKv{R_aN|f*_2dt_(?&aS-eZBZH37f@yRFC>}jS zji1ig8BiW%jN4$)0D~qNv{L)+CU3LUA|<4`Ov!QWb7w!BCJaZMBqm7FhjMoc6DQDO z3!P^`hJ<-Kf6rj{5dKJj;7{>sf=>&4+Nt$+v(3e6EJ@u(fW)t$6V}98^oKHZ4ii3F z?4Totz!Kq}&eH!SGV3NZDQ2)yn(uT450qvr`$Z;57D{BPwb1Rh4`uNJXr4vel^J24 z&fqs2Tea70cbEEHF;^4EiGdxb${=M&B>7o-E7>~ih{gxr`Awef);MP*hME}1+CyJL z$NDbX(4lDj^p|`KvqNLxCgQE7dRz65>T{}hhr#82_zJI8^7|}RnAiz^cfWe=&h^{X z)Vg=={(NfPyf%L=b*uMo-M;(IgKFyDxO?Z`?dp8>P}ZNr7O$bjlw4ii8T2;Hha|G)&U5bom%WMqel8Qq|((yaP zSa*{*x`?LhzsrEw5B;4u=y!M=hfCZKV_$_OzZuD{e{W4LcO#!uG>ST;G$P9)*f^%p$ z(Bc+4scmYB7U)bzZzKkKQ(7$>PbVQhQP` z_IR4V^WgUUEt*IRy+Qg4u&6MUJO)sN74!C@+A@3FZlIwULn*%*HDwUo$0$CJPO~#t zLSL|SLm&UzFs!ef{4TtHe7~7q9_~`n#S7S?K~ZV=Bel9{J8E4YPqdb{1+{ML8~Ts6 z=!~UFW5pAfb&dYgr1?$E{ZxyLpJ+Su2I!3u`j7U}8yUT{U>wG#O6|3O5G$VQOBGvu z4%?UPc={jrG$9#5KCIVyHZj$&DiY@JBGoPRZ>D01{MfE3Ozr@pCA!)aRjiUqv2sePP`;U5H}ZJm#2X%QvAe(0leY2`fhaOm_Nu%YH zJO;E(o=bFjev>4^po0?a_#5rF2I(T93thSP1w{!}0?p1MhEdh{<T*^73JRqNCG=1Qu)@NjsCHHG7hmHyKlFaeDC^JM ziXMgSCi5v>&Z>mlP}dF0;W76Y_*yq&e&s@%uT`rz1FDw9OSB=CJSvBX*8!zYjFi(@ z&Z3>YB)a`x>LIxZ89#2yW$`?bQ!S^eUzK-+Oij4Fx~!z7>$k7f-V0RLxnE7oL3YU; z+&jc|T1gd^ph8Lb=u&fuOI5b3>xB3=;jBf(*(~=|iSP~TE>Nc;57o1R3f$CdLZ;v^ zYH&dXXr-tEPim`TCbc`E#A!$?N`7f!L?s|vl_K1gdc8oB3lPtjDBSr`fZPd^KgVE6 z+c2a`rU>tFm_*+)wzZquhi@UonOo*ID%BNJ%pfTuLC|>1W~f`Y4Fn%+!-ms3iADIt zrhXAYRM#HP$Q)8AIftF4Q2!sq!qN!8ezHbsMeLbAij5K06q8gLh|B2SLsK6eT;0lRUm0XEy7BMgg>rGSP`;yR8lc(pGcme~ z7L$Vx?;YG^Ix}!rfYxwVp!XY2!`M+bEqCPZP>s~xp@!H(rR3q21sRpfv{baH zVp@}Wi%MowRR|E1l`Y?mEfi{oHG$fBpI%5@e?wE(WDVyXsT2oH?N*A0_z_4CGDjh0 z=N9lP;<5yL5{XehF?s>caWY;q!PVV+|;MESClv5eJ=L-HRdceR~pU%V3q>*F`FJhjB(xwyml>jhj& zeRZF?;#ZD|2cT5BWq-a>Sx7~8`V5t25K11I?Rf;z=cFu=s&u1bgpJ!O7g;jd$s;N; zp~zS9L|K>GSgq%@$zFR!ltK{7g8$z!9e)>{hHy_8pb6cg|5pyKLuCr#Xs$kqFmnP= z3seq;Z+&t!cn^(yci&2GU#!VOj0!9&oV51xwHrj1G4WS4C1j)`OAw}VNxY0lmgCfy zXC4dk<<#Q%?5sYk2vSSx$h95{l`KV5$`IsL!lk)9G9oUT_!cdcgef-H%ak?|tNRUn zBEYT`<5G{WHhA`JIlCHI%To5TZRRlEi#Arh$bj@s*{c=Yey)l`$qOz`YlzjT?efTEsXqCB2Z(qY`OXbHE3|c89c3V T%Q4423py>E6K44r+R6U{)Jm4P literal 0 HcmV?d00001 diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/sansio/__pycache__/request.cpython-38.pyc b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/sansio/__pycache__/request.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1a33e81fd6816cad649e242d94a9bff4c17a5240 GIT binary patch literal 17213 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 PLQytGarfx|_?!D*V9%O! literal 0 HcmV?d00001 diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/sansio/__pycache__/response.cpython-38.pyc b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/sansio/__pycache__/response.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2ce39fdefd238ba006c1a9cd89e0f2132ee7e2d2 GIT binary patch literal 21134 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 literal 0 HcmV?d00001 diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/sansio/__pycache__/utils.cpython-38.pyc b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/sansio/__pycache__/utils.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1012c6bd85e46d1ef280bae3d5592afdbf07ffcc GIT binary patch literal 3911 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 IKlk~60FMDenE(I) literal 0 HcmV?d00001 diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/sansio/multipart.py b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/sansio/multipart.py new file mode 100644 index 000000000..bb8ab3455 --- /dev/null +++ b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/sansio/multipart.py @@ -0,0 +1,260 @@ +import re +from dataclasses import dataclass +from enum import auto +from enum import Enum +from typing import cast +from typing import List +from typing import Optional +from typing import Tuple + +from .._internal import _to_bytes +from .._internal import _to_str +from ..datastructures import Headers +from ..exceptions import RequestEntityTooLarge +from ..http import parse_options_header + + +class Event: + pass + + +@dataclass(frozen=True) +class Preamble(Event): + data: bytes + + +@dataclass(frozen=True) +class Field(Event): + name: str + headers: Headers + + +@dataclass(frozen=True) +class File(Event): + name: str + filename: str + headers: Headers + + +@dataclass(frozen=True) +class Data(Event): + data: bytes + more_data: bool + + +@dataclass(frozen=True) +class Epilogue(Event): + data: bytes + + +class NeedData(Event): + pass + + +NEED_DATA = NeedData() + + +class State(Enum): + PREAMBLE = auto() + PART = auto() + DATA = auto() + EPILOGUE = auto() + COMPLETE = auto() + + +# Multipart line breaks MUST be CRLF (\r\n) by RFC-7578, except that +# many implementations break this and either use CR or LF alone. +LINE_BREAK = b"(?:\r\n|\n|\r)" +BLANK_LINE_RE = re.compile(b"(?:\r\n\r\n|\r\r|\n\n)", re.MULTILINE) +LINE_BREAK_RE = re.compile(LINE_BREAK, re.MULTILINE) +# Header values can be continued via a space or tab after the linebreak, as +# per RFC2231 +HEADER_CONTINUATION_RE = re.compile(b"%s[ \t]" % LINE_BREAK, re.MULTILINE) + + +class MultipartDecoder: + """Decodes a multipart message as bytes into Python events. + + The part data is returned as available to allow the caller to save + the data from memory to disk, if desired. + """ + + def __init__( + self, + boundary: bytes, + max_form_memory_size: Optional[int] = None, + ) -> None: + self.buffer = bytearray() + self.complete = False + self.max_form_memory_size = max_form_memory_size + self.state = State.PREAMBLE + self.boundary = boundary + + # Note in the below \h i.e. horizontal whitespace is used + # as [^\S\n\r] as \h isn't supported in python. + + # The preamble must end with a boundary where the boundary is + # prefixed by a line break, RFC2046. Except that many + # implementations including Werkzeug's tests omit the line + # break prefix. In addition the first boundary could be the + # epilogue boundary (for empty form-data) hence the matching + # group to understand if it is an epilogue boundary. + self.preamble_re = re.compile( + br"%s?--%s(--[^\S\n\r]*%s?|[^\S\n\r]*%s)" + % (LINE_BREAK, re.escape(boundary), LINE_BREAK, LINE_BREAK), + re.MULTILINE, + ) + # A boundary must include a line break prefix and suffix, and + # may include trailing whitespace. In addition the boundary + # could be the epilogue boundary hence the matching group to + # understand if it is an epilogue boundary. + self.boundary_re = re.compile( + br"%s--%s(--[^\S\n\r]*%s?|[^\S\n\r]*%s)" + % (LINE_BREAK, re.escape(boundary), LINE_BREAK, LINE_BREAK), + re.MULTILINE, + ) + + def last_newline(self) -> int: + try: + last_nl = self.buffer.rindex(b"\n") + except ValueError: + last_nl = len(self.buffer) + try: + last_cr = self.buffer.rindex(b"\r") + except ValueError: + last_cr = len(self.buffer) + + return min(last_nl, last_cr) + + def receive_data(self, data: Optional[bytes]) -> None: + if data is None: + self.complete = True + elif ( + self.max_form_memory_size is not None + and len(self.buffer) + len(data) > self.max_form_memory_size + ): + raise RequestEntityTooLarge() + else: + self.buffer.extend(data) + + def next_event(self) -> Event: + event: Event = NEED_DATA + + if self.state == State.PREAMBLE: + match = self.preamble_re.search(self.buffer) + if match is not None: + if match.group(1).startswith(b"--"): + self.state = State.EPILOGUE + else: + self.state = State.PART + data = bytes(self.buffer[: match.start()]) + del self.buffer[: match.end()] + event = Preamble(data=data) + + elif self.state == State.PART: + match = BLANK_LINE_RE.search(self.buffer) + if match is not None: + headers = self._parse_headers(self.buffer[: match.start()]) + del self.buffer[: match.end()] + + if "content-disposition" not in headers: + raise ValueError("Missing Content-Disposition header") + + disposition, extra = parse_options_header( + headers["content-disposition"] + ) + name = cast(str, extra.get("name")) + filename = extra.get("filename") + if filename is not None: + event = File( + filename=filename, + headers=headers, + name=name, + ) + else: + event = Field( + headers=headers, + name=name, + ) + self.state = State.DATA + + elif self.state == State.DATA: + if self.buffer.find(b"--" + self.boundary) == -1: + # No complete boundary in the buffer, but there may be + # a partial boundary at the end. As the boundary + # starts with either a nl or cr find the earliest and + # return up to that as data. + data_length = del_index = self.last_newline() + more_data = True + else: + match = self.boundary_re.search(self.buffer) + if match is not None: + if match.group(1).startswith(b"--"): + self.state = State.EPILOGUE + else: + self.state = State.PART + data_length = match.start() + del_index = match.end() + else: + data_length = del_index = self.last_newline() + more_data = match is None + + data = bytes(self.buffer[:data_length]) + del self.buffer[:del_index] + if data or not more_data: + event = Data(data=data, more_data=more_data) + + elif self.state == State.EPILOGUE and self.complete: + event = Epilogue(data=bytes(self.buffer)) + del self.buffer[:] + self.state = State.COMPLETE + + if self.complete and isinstance(event, NeedData): + raise ValueError(f"Invalid form-data cannot parse beyond {self.state}") + + return event + + def _parse_headers(self, data: bytes) -> Headers: + headers: List[Tuple[str, str]] = [] + # Merge the continued headers into one line + data = HEADER_CONTINUATION_RE.sub(b" ", data) + # Now there is one header per line + for line in data.splitlines(): + if line.strip() != b"": + name, value = _to_str(line).strip().split(":", 1) + headers.append((name.strip(), value.strip())) + return Headers(headers) + + +class MultipartEncoder: + def __init__(self, boundary: bytes) -> None: + self.boundary = boundary + self.state = State.PREAMBLE + + def send_event(self, event: Event) -> bytes: + if isinstance(event, Preamble) and self.state == State.PREAMBLE: + self.state = State.PART + return event.data + elif isinstance(event, (Field, File)) and self.state in { + State.PREAMBLE, + State.PART, + State.DATA, + }: + self.state = State.DATA + data = b"\r\n--" + self.boundary + b"\r\n" + data += b'Content-Disposition: form-data; name="%s"' % _to_bytes(event.name) + if isinstance(event, File): + data += b'; filename="%s"' % _to_bytes(event.filename) + data += b"\r\n" + for name, value in cast(Field, event).headers: + if name.lower() != "content-disposition": + data += _to_bytes(f"{name}: {value}\r\n") + data += b"\r\n" + return data + elif isinstance(event, Data) and self.state == State.DATA: + return event.data + elif isinstance(event, Epilogue): + self.state = State.COMPLETE + return b"\r\n--" + self.boundary + b"--\r\n" + event.data + else: + raise ValueError(f"Cannot generate {event} in state: {self.state}") diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/sansio/request.py b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/sansio/request.py new file mode 100644 index 000000000..2c21a2134 --- /dev/null +++ b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/sansio/request.py @@ -0,0 +1,548 @@ +import typing as t +from datetime import datetime + +from .._internal import _to_str +from ..datastructures import Accept +from ..datastructures import Authorization +from ..datastructures import CharsetAccept +from ..datastructures import ETags +from ..datastructures import Headers +from ..datastructures import HeaderSet +from ..datastructures import IfRange +from ..datastructures import ImmutableList +from ..datastructures import ImmutableMultiDict +from ..datastructures import LanguageAccept +from ..datastructures import MIMEAccept +from ..datastructures import MultiDict +from ..datastructures import Range +from ..datastructures import RequestCacheControl +from ..http import parse_accept_header +from ..http import parse_authorization_header +from ..http import parse_cache_control_header +from ..http import parse_cookie +from ..http import parse_date +from ..http import parse_etags +from ..http import parse_if_range_header +from ..http import parse_list_header +from ..http import parse_options_header +from ..http import parse_range_header +from ..http import parse_set_header +from ..urls import url_decode +from ..user_agent import UserAgent +from ..useragents import _UserAgent as _DeprecatedUserAgent +from ..utils import cached_property +from ..utils import header_property +from .utils import get_current_url +from .utils import get_host + + +class Request: + """Represents the non-IO parts of a HTTP request, including the + method, URL info, and headers. + + This class is not meant for general use. It should only be used when + implementing WSGI, ASGI, or another HTTP application spec. Werkzeug + provides a WSGI implementation at :cls:`werkzeug.wrappers.Request`. + + :param method: The method the request was made with, such as + ``GET``. + :param scheme: The URL scheme of the protocol the request used, such + as ``https`` or ``wss``. + :param server: The address of the server. ``(host, port)``, + ``(path, None)`` for unix sockets, or ``None`` if not known. + :param root_path: The prefix that the application is mounted under. + This is prepended to generated URLs, but is not part of route + matching. + :param path: The path part of the URL after ``root_path``. + :param query_string: The part of the URL after the "?". + :param headers: The headers received with the request. + :param remote_addr: The address of the client sending the request. + + .. versionadded:: 2.0 + """ + + #: The charset used to decode most data in the request. + charset = "utf-8" + + #: the error handling procedure for errors, defaults to 'replace' + encoding_errors = "replace" + + #: the class to use for `args` and `form`. The default is an + #: :class:`~werkzeug.datastructures.ImmutableMultiDict` which supports + #: multiple values per key. alternatively it makes sense to use an + #: :class:`~werkzeug.datastructures.ImmutableOrderedMultiDict` which + #: preserves order or a :class:`~werkzeug.datastructures.ImmutableDict` + #: which is the fastest but only remembers the last key. It is also + #: possible to use mutable structures, but this is not recommended. + #: + #: .. versionadded:: 0.6 + parameter_storage_class: t.Type[MultiDict] = ImmutableMultiDict + + #: The type to be used for dict values from the incoming WSGI + #: environment. (For example for :attr:`cookies`.) By default an + #: :class:`~werkzeug.datastructures.ImmutableMultiDict` is used. + #: + #: .. versionchanged:: 1.0.0 + #: Changed to ``ImmutableMultiDict`` to support multiple values. + #: + #: .. versionadded:: 0.6 + dict_storage_class: t.Type[MultiDict] = ImmutableMultiDict + + #: the type to be used for list values from the incoming WSGI environment. + #: By default an :class:`~werkzeug.datastructures.ImmutableList` is used + #: (for example for :attr:`access_list`). + #: + #: .. versionadded:: 0.6 + list_storage_class: t.Type[t.List] = ImmutableList + + user_agent_class = _DeprecatedUserAgent + """The class used and returned by the :attr:`user_agent` property to + parse the header. Defaults to + :class:`~werkzeug.user_agent.UserAgent`, which does no parsing. An + extension can provide a subclass that uses a parser to provide other + data. + + .. versionadded:: 2.0 + """ + + #: Valid host names when handling requests. By default all hosts are + #: trusted, which means that whatever the client says the host is + #: will be accepted. + #: + #: Because ``Host`` and ``X-Forwarded-Host`` headers can be set to + #: any value by a malicious client, it is recommended to either set + #: this property or implement similar validation in the proxy (if + #: the application is being run behind one). + #: + #: .. versionadded:: 0.9 + trusted_hosts: t.Optional[t.List[str]] = None + + def __init__( + self, + method: str, + scheme: str, + server: t.Optional[t.Tuple[str, t.Optional[int]]], + root_path: str, + path: str, + query_string: bytes, + headers: Headers, + remote_addr: t.Optional[str], + ) -> None: + #: The method the request was made with, such as ``GET``. + self.method = method.upper() + #: The URL scheme of the protocol the request used, such as + #: ``https`` or ``wss``. + self.scheme = scheme + #: The address of the server. ``(host, port)``, ``(path, None)`` + #: for unix sockets, or ``None`` if not known. + self.server = server + #: The prefix that the application is mounted under, without a + #: trailing slash. :attr:`path` comes after this. + self.root_path = root_path.rstrip("/") + #: The path part of the URL after :attr:`root_path`. This is the + #: path used for routing within the application. + self.path = "/" + path.lstrip("/") + #: The part of the URL after the "?". This is the raw value, use + #: :attr:`args` for the parsed values. + self.query_string = query_string + #: The headers received with the request. + self.headers = headers + #: The address of the client sending the request. + self.remote_addr = remote_addr + + def __repr__(self) -> str: + try: + url = self.url + except Exception as e: + url = f"(invalid URL: {e})" + + return f"<{type(self).__name__} {url!r} [{self.method}]>" + + @property + def url_charset(self) -> str: + """The charset that is assumed for URLs. Defaults to the value + of :attr:`charset`. + + .. versionadded:: 0.6 + """ + return self.charset + + @cached_property + def args(self) -> "MultiDict[str, str]": + """The parsed URL parameters (the part in the URL after the question + mark). + + By default an + :class:`~werkzeug.datastructures.ImmutableMultiDict` + is returned from this function. This can be changed by setting + :attr:`parameter_storage_class` to a different type. This might + be necessary if the order of the form data is important. + """ + return url_decode( + self.query_string, + self.url_charset, + errors=self.encoding_errors, + cls=self.parameter_storage_class, + ) + + @cached_property + def access_route(self) -> t.List[str]: + """If a forwarded header exists this is a list of all ip addresses + from the client ip to the last proxy server. + """ + if "X-Forwarded-For" in self.headers: + return self.list_storage_class( + parse_list_header(self.headers["X-Forwarded-For"]) + ) + elif self.remote_addr is not None: + return self.list_storage_class([self.remote_addr]) + return self.list_storage_class() + + @cached_property + def full_path(self) -> str: + """Requested path, including the query string.""" + return f"{self.path}?{_to_str(self.query_string, self.url_charset)}" + + @property + def is_secure(self) -> bool: + """``True`` if the request was made with a secure protocol + (HTTPS or WSS). + """ + return self.scheme in {"https", "wss"} + + @cached_property + def url(self) -> str: + """The full request URL with the scheme, host, root path, path, + and query string.""" + return get_current_url( + self.scheme, self.host, self.root_path, self.path, self.query_string + ) + + @cached_property + def base_url(self) -> str: + """Like :attr:`url` but without the query string.""" + return get_current_url(self.scheme, self.host, self.root_path, self.path) + + @cached_property + def root_url(self) -> str: + """The request URL scheme, host, and root path. This is the root + that the application is accessed from. + """ + return get_current_url(self.scheme, self.host, self.root_path) + + @cached_property + def host_url(self) -> str: + """The request URL scheme and host only.""" + return get_current_url(self.scheme, self.host) + + @cached_property + def host(self) -> str: + """The host name the request was made to, including the port if + it's non-standard. Validated with :attr:`trusted_hosts`. + """ + return get_host( + self.scheme, self.headers.get("host"), self.server, self.trusted_hosts + ) + + @cached_property + def cookies(self) -> "ImmutableMultiDict[str, str]": + """A :class:`dict` with the contents of all cookies transmitted with + the request.""" + wsgi_combined_cookie = ";".join(self.headers.getlist("Cookie")) + return parse_cookie( # type: ignore + wsgi_combined_cookie, + self.charset, + self.encoding_errors, + cls=self.dict_storage_class, + ) + + # Common Descriptors + + content_type = header_property[str]( + "Content-Type", + doc="""The Content-Type entity-header field indicates the media + type of the entity-body sent to the recipient or, in the case of + the HEAD method, the media type that would have been sent had + the request been a GET.""", + read_only=True, + ) + + @cached_property + def content_length(self) -> t.Optional[int]: + """The Content-Length entity-header field indicates the size of the + entity-body in bytes or, in the case of the HEAD method, the size of + the entity-body that would have been sent had the request been a + GET. + """ + if self.headers.get("Transfer-Encoding", "") == "chunked": + return None + + content_length = self.headers.get("Content-Length") + if content_length is not None: + try: + return max(0, int(content_length)) + except (ValueError, TypeError): + pass + + return None + + content_encoding = header_property[str]( + "Content-Encoding", + doc="""The Content-Encoding entity-header field is used as a + modifier to the media-type. When present, its value indicates + what additional content codings have been applied to the + entity-body, and thus what decoding mechanisms must be applied + in order to obtain the media-type referenced by the Content-Type + header field. + + .. versionadded:: 0.9""", + read_only=True, + ) + content_md5 = header_property[str]( + "Content-MD5", + doc="""The Content-MD5 entity-header field, as defined in + RFC 1864, is an MD5 digest of the entity-body for the purpose of + providing an end-to-end message integrity check (MIC) of the + entity-body. (Note: a MIC is good for detecting accidental + modification of the entity-body in transit, but is not proof + against malicious attacks.) + + .. versionadded:: 0.9""", + read_only=True, + ) + referrer = header_property[str]( + "Referer", + doc="""The Referer[sic] request-header field allows the client + to specify, for the server's benefit, the address (URI) of the + resource from which the Request-URI was obtained (the + "referrer", although the header field is misspelled).""", + read_only=True, + ) + date = header_property( + "Date", + None, + parse_date, + doc="""The Date general-header field represents the date and + time at which the message was originated, having the same + semantics as orig-date in RFC 822. + + .. versionchanged:: 2.0 + The datetime object is timezone-aware. + """, + read_only=True, + ) + max_forwards = header_property( + "Max-Forwards", + None, + int, + doc="""The Max-Forwards request-header field provides a + mechanism with the TRACE and OPTIONS methods to limit the number + of proxies or gateways that can forward the request to the next + inbound server.""", + read_only=True, + ) + + def _parse_content_type(self) -> None: + if not hasattr(self, "_parsed_content_type"): + self._parsed_content_type = parse_options_header( + self.headers.get("Content-Type", "") + ) + + @property + def mimetype(self) -> str: + """Like :attr:`content_type`, but without parameters (eg, without + charset, type etc.) and always lowercase. For example if the content + type is ``text/HTML; charset=utf-8`` the mimetype would be + ``'text/html'``. + """ + self._parse_content_type() + return self._parsed_content_type[0].lower() + + @property + def mimetype_params(self) -> t.Dict[str, str]: + """The mimetype parameters as dict. For example if the content + type is ``text/html; charset=utf-8`` the params would be + ``{'charset': 'utf-8'}``. + """ + self._parse_content_type() + return self._parsed_content_type[1] + + @cached_property + def pragma(self) -> HeaderSet: + """The Pragma general-header field is used to include + implementation-specific directives that might apply to any recipient + along the request/response chain. All pragma directives specify + optional behavior from the viewpoint of the protocol; however, some + systems MAY require that behavior be consistent with the directives. + """ + return parse_set_header(self.headers.get("Pragma", "")) + + # Accept + + @cached_property + def accept_mimetypes(self) -> MIMEAccept: + """List of mimetypes this client supports as + :class:`~werkzeug.datastructures.MIMEAccept` object. + """ + return parse_accept_header(self.headers.get("Accept"), MIMEAccept) + + @cached_property + def accept_charsets(self) -> CharsetAccept: + """List of charsets this client supports as + :class:`~werkzeug.datastructures.CharsetAccept` object. + """ + return parse_accept_header(self.headers.get("Accept-Charset"), CharsetAccept) + + @cached_property + def accept_encodings(self) -> Accept: + """List of encodings this client accepts. Encodings in a HTTP term + are compression encodings such as gzip. For charsets have a look at + :attr:`accept_charset`. + """ + return parse_accept_header(self.headers.get("Accept-Encoding")) + + @cached_property + def accept_languages(self) -> LanguageAccept: + """List of languages this client accepts as + :class:`~werkzeug.datastructures.LanguageAccept` object. + + .. versionchanged 0.5 + In previous versions this was a regular + :class:`~werkzeug.datastructures.Accept` object. + """ + return parse_accept_header(self.headers.get("Accept-Language"), LanguageAccept) + + # ETag + + @cached_property + def cache_control(self) -> RequestCacheControl: + """A :class:`~werkzeug.datastructures.RequestCacheControl` object + for the incoming cache control headers. + """ + cache_control = self.headers.get("Cache-Control") + return parse_cache_control_header(cache_control, None, RequestCacheControl) + + @cached_property + def if_match(self) -> ETags: + """An object containing all the etags in the `If-Match` header. + + :rtype: :class:`~werkzeug.datastructures.ETags` + """ + return parse_etags(self.headers.get("If-Match")) + + @cached_property + def if_none_match(self) -> ETags: + """An object containing all the etags in the `If-None-Match` header. + + :rtype: :class:`~werkzeug.datastructures.ETags` + """ + return parse_etags(self.headers.get("If-None-Match")) + + @cached_property + def if_modified_since(self) -> t.Optional[datetime]: + """The parsed `If-Modified-Since` header as a datetime object. + + .. versionchanged:: 2.0 + The datetime object is timezone-aware. + """ + return parse_date(self.headers.get("If-Modified-Since")) + + @cached_property + def if_unmodified_since(self) -> t.Optional[datetime]: + """The parsed `If-Unmodified-Since` header as a datetime object. + + .. versionchanged:: 2.0 + The datetime object is timezone-aware. + """ + return parse_date(self.headers.get("If-Unmodified-Since")) + + @cached_property + def if_range(self) -> IfRange: + """The parsed ``If-Range`` header. + + .. versionchanged:: 2.0 + ``IfRange.date`` is timezone-aware. + + .. versionadded:: 0.7 + """ + return parse_if_range_header(self.headers.get("If-Range")) + + @cached_property + def range(self) -> t.Optional[Range]: + """The parsed `Range` header. + + .. versionadded:: 0.7 + + :rtype: :class:`~werkzeug.datastructures.Range` + """ + return parse_range_header(self.headers.get("Range")) + + # User Agent + + @cached_property + def user_agent(self) -> UserAgent: + """The user agent. Use ``user_agent.string`` to get the header + value. Set :attr:`user_agent_class` to a subclass of + :class:`~werkzeug.user_agent.UserAgent` to provide parsing for + the other properties or other extended data. + + .. versionchanged:: 2.0 + The built in parser is deprecated and will be removed in + Werkzeug 2.1. A ``UserAgent`` subclass must be set to parse + data from the string. + """ + return self.user_agent_class(self.headers.get("User-Agent", "")) + + # Authorization + + @cached_property + def authorization(self) -> t.Optional[Authorization]: + """The `Authorization` object in parsed form.""" + return parse_authorization_header(self.headers.get("Authorization")) + + # CORS + + origin = header_property[str]( + "Origin", + doc=( + "The host that the request originated from. Set" + " :attr:`~CORSResponseMixin.access_control_allow_origin` on" + " the response to indicate which origins are allowed." + ), + read_only=True, + ) + + access_control_request_headers = header_property( + "Access-Control-Request-Headers", + load_func=parse_set_header, + doc=( + "Sent with a preflight request to indicate which headers" + " will be sent with the cross origin request. Set" + " :attr:`~CORSResponseMixin.access_control_allow_headers`" + " on the response to indicate which headers are allowed." + ), + read_only=True, + ) + + access_control_request_method = header_property[str]( + "Access-Control-Request-Method", + doc=( + "Sent with a preflight request to indicate which method" + " will be used for the cross origin request. Set" + " :attr:`~CORSResponseMixin.access_control_allow_methods`" + " on the response to indicate which methods are allowed." + ), + read_only=True, + ) + + @property + def is_json(self) -> bool: + """Check if the mimetype indicates JSON data, either + :mimetype:`application/json` or :mimetype:`application/*+json`. + """ + mt = self.mimetype + return ( + mt == "application/json" + or mt.startswith("application/") + and mt.endswith("+json") + ) diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/sansio/response.py b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/sansio/response.py new file mode 100644 index 000000000..aedfcb043 --- /dev/null +++ b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/sansio/response.py @@ -0,0 +1,656 @@ +import typing +import typing as t +from datetime import datetime +from datetime import timedelta +from datetime import timezone +from http import HTTPStatus + +from .._internal import _to_str +from ..datastructures import Headers +from ..datastructures import HeaderSet +from ..http import dump_cookie +from ..http import HTTP_STATUS_CODES +from ..utils import get_content_type +from werkzeug.datastructures import CallbackDict +from werkzeug.datastructures import ContentRange +from werkzeug.datastructures import ResponseCacheControl +from werkzeug.datastructures import WWWAuthenticate +from werkzeug.http import COEP +from werkzeug.http import COOP +from werkzeug.http import dump_age +from werkzeug.http import dump_csp_header +from werkzeug.http import dump_header +from werkzeug.http import dump_options_header +from werkzeug.http import http_date +from werkzeug.http import parse_age +from werkzeug.http import parse_cache_control_header +from werkzeug.http import parse_content_range_header +from werkzeug.http import parse_csp_header +from werkzeug.http import parse_date +from werkzeug.http import parse_options_header +from werkzeug.http import parse_set_header +from werkzeug.http import parse_www_authenticate_header +from werkzeug.http import quote_etag +from werkzeug.http import unquote_etag +from werkzeug.utils import header_property + + +def _set_property(name: str, doc: t.Optional[str] = None) -> property: + def fget(self: "Response") -> HeaderSet: + def on_update(header_set: HeaderSet) -> None: + if not header_set and name in self.headers: + del self.headers[name] + elif header_set: + self.headers[name] = header_set.to_header() + + return parse_set_header(self.headers.get(name), on_update) + + def fset( + self: "Response", + value: t.Optional[ + t.Union[str, t.Dict[str, t.Union[str, int]], t.Iterable[str]] + ], + ) -> None: + if not value: + del self.headers[name] + elif isinstance(value, str): + self.headers[name] = value + else: + self.headers[name] = dump_header(value) + + return property(fget, fset, doc=doc) + + +class Response: + """Represents the non-IO parts of an HTTP response, specifically the + status and headers but not the body. + + This class is not meant for general use. It should only be used when + implementing WSGI, ASGI, or another HTTP application spec. Werkzeug + provides a WSGI implementation at :cls:`werkzeug.wrappers.Response`. + + :param status: The status code for the response. Either an int, in + which case the default status message is added, or a string in + the form ``{code} {message}``, like ``404 Not Found``. Defaults + to 200. + :param headers: A :class:`~werkzeug.datastructures.Headers` object, + or a list of ``(key, value)`` tuples that will be converted to a + ``Headers`` object. + :param mimetype: The mime type (content type without charset or + other parameters) of the response. If the value starts with + ``text/`` (or matches some other special cases), the charset + will be added to create the ``content_type``. + :param content_type: The full content type of the response. + Overrides building the value from ``mimetype``. + + .. versionadded:: 2.0 + """ + + #: the charset of the response. + charset = "utf-8" + + #: the default status if none is provided. + default_status = 200 + + #: the default mimetype if none is provided. + default_mimetype = "text/plain" + + #: Warn if a cookie header exceeds this size. The default, 4093, should be + #: safely `supported by most browsers `_. A cookie larger than + #: this size will still be sent, but it may be ignored or handled + #: incorrectly by some browsers. Set to 0 to disable this check. + #: + #: .. versionadded:: 0.13 + #: + #: .. _`cookie`: http://browsercookielimits.squawky.net/ + max_cookie_size = 4093 + + # A :class:`Headers` object representing the response headers. + headers: Headers + + def __init__( + self, + status: t.Optional[t.Union[int, str, HTTPStatus]] = None, + headers: t.Optional[ + t.Union[ + t.Mapping[str, t.Union[str, int, t.Iterable[t.Union[str, int]]]], + t.Iterable[t.Tuple[str, t.Union[str, int]]], + ] + ] = None, + mimetype: t.Optional[str] = None, + content_type: t.Optional[str] = None, + ) -> None: + if isinstance(headers, Headers): + self.headers = headers + elif not headers: + self.headers = Headers() + else: + self.headers = Headers(headers) + + if content_type is None: + if mimetype is None and "content-type" not in self.headers: + mimetype = self.default_mimetype + if mimetype is not None: + mimetype = get_content_type(mimetype, self.charset) + content_type = mimetype + if content_type is not None: + self.headers["Content-Type"] = content_type + if status is None: + status = self.default_status + self.status = status # type: ignore + + def __repr__(self) -> str: + return f"<{type(self).__name__} [{self.status}]>" + + @property + def status_code(self) -> int: + """The HTTP status code as a number.""" + return self._status_code + + @status_code.setter + def status_code(self, code: int) -> None: + self.status = code # type: ignore + + @property + def status(self) -> str: + """The HTTP status code as a string.""" + return self._status + + @status.setter + def status(self, value: t.Union[str, int, HTTPStatus]) -> None: + if not isinstance(value, (str, bytes, int, HTTPStatus)): + raise TypeError("Invalid status argument") + + self._status, self._status_code = self._clean_status(value) + + def _clean_status(self, value: t.Union[str, int, HTTPStatus]) -> t.Tuple[str, int]: + if isinstance(value, HTTPStatus): + value = int(value) + status = _to_str(value, self.charset) + split_status = status.split(None, 1) + + if len(split_status) == 0: + raise ValueError("Empty status argument") + + if len(split_status) > 1: + if split_status[0].isdigit(): + # code and message + return status, int(split_status[0]) + + # multi-word message + return f"0 {status}", 0 + + if split_status[0].isdigit(): + # code only + status_code = int(split_status[0]) + + try: + status = f"{status_code} {HTTP_STATUS_CODES[status_code].upper()}" + except KeyError: + status = f"{status_code} UNKNOWN" + + return status, status_code + + # one-word message + return f"0 {status}", 0 + + def set_cookie( + self, + key: str, + value: str = "", + max_age: t.Optional[t.Union[timedelta, int]] = None, + expires: t.Optional[t.Union[str, datetime, int, float]] = None, + path: t.Optional[str] = "/", + domain: t.Optional[str] = None, + secure: bool = False, + httponly: bool = False, + samesite: t.Optional[str] = None, + ) -> None: + """Sets a cookie. + + A warning is raised if the size of the cookie header exceeds + :attr:`max_cookie_size`, but the header will still be set. + + :param key: the key (name) of the cookie to be set. + :param value: the value of the cookie. + :param max_age: should be a number of seconds, or `None` (default) if + the cookie should last only as long as the client's + browser session. + :param expires: should be a `datetime` object or UNIX timestamp. + :param path: limits the cookie to a given path, per default it will + span the whole domain. + :param domain: if you want to set a cross-domain cookie. For example, + ``domain=".example.com"`` will set a cookie that is + readable by the domain ``www.example.com``, + ``foo.example.com`` etc. Otherwise, a cookie will only + be readable by the domain that set it. + :param secure: If ``True``, the cookie will only be available + via HTTPS. + :param httponly: Disallow JavaScript access to the cookie. + :param samesite: Limit the scope of the cookie to only be + attached to requests that are "same-site". + """ + self.headers.add( + "Set-Cookie", + dump_cookie( + key, + value=value, + max_age=max_age, + expires=expires, + path=path, + domain=domain, + secure=secure, + httponly=httponly, + charset=self.charset, + max_size=self.max_cookie_size, + samesite=samesite, + ), + ) + + def delete_cookie( + self, + key: str, + path: str = "/", + domain: t.Optional[str] = None, + secure: bool = False, + httponly: bool = False, + samesite: t.Optional[str] = None, + ) -> None: + """Delete a cookie. Fails silently if key doesn't exist. + + :param key: the key (name) of the cookie to be deleted. + :param path: if the cookie that should be deleted was limited to a + path, the path has to be defined here. + :param domain: if the cookie that should be deleted was limited to a + domain, that domain has to be defined here. + :param secure: If ``True``, the cookie will only be available + via HTTPS. + :param httponly: Disallow JavaScript access to the cookie. + :param samesite: Limit the scope of the cookie to only be + attached to requests that are "same-site". + """ + self.set_cookie( + key, + expires=0, + max_age=0, + path=path, + domain=domain, + secure=secure, + httponly=httponly, + samesite=samesite, + ) + + @property + def is_json(self) -> bool: + """Check if the mimetype indicates JSON data, either + :mimetype:`application/json` or :mimetype:`application/*+json`. + """ + mt = self.mimetype + return mt is not None and ( + mt == "application/json" + or mt.startswith("application/") + and mt.endswith("+json") + ) + + # Common Descriptors + + @property + def mimetype(self) -> t.Optional[str]: + """The mimetype (content type without charset etc.)""" + ct = self.headers.get("content-type") + + if ct: + return ct.split(";")[0].strip() + else: + return None + + @mimetype.setter + def mimetype(self, value: str) -> None: + self.headers["Content-Type"] = get_content_type(value, self.charset) + + @property + def mimetype_params(self) -> t.Dict[str, str]: + """The mimetype parameters as dict. For example if the + content type is ``text/html; charset=utf-8`` the params would be + ``{'charset': 'utf-8'}``. + + .. versionadded:: 0.5 + """ + + def on_update(d: t.Dict[str, str]) -> None: + self.headers["Content-Type"] = dump_options_header(self.mimetype, d) + + d = parse_options_header(self.headers.get("content-type", ""))[1] + return CallbackDict(d, on_update) + + location = header_property[str]( + "Location", + doc="""The Location response-header field is used to redirect + the recipient to a location other than the Request-URI for + completion of the request or identification of a new + resource.""", + ) + age = header_property( + "Age", + None, + parse_age, + dump_age, # type: ignore + doc="""The Age response-header field conveys the sender's + estimate of the amount of time since the response (or its + revalidation) was generated at the origin server. + + Age values are non-negative decimal integers, representing time + in seconds.""", + ) + content_type = header_property[str]( + "Content-Type", + doc="""The Content-Type entity-header field indicates the media + type of the entity-body sent to the recipient or, in the case of + the HEAD method, the media type that would have been sent had + the request been a GET.""", + ) + content_length = header_property( + "Content-Length", + None, + int, + str, + doc="""The Content-Length entity-header field indicates the size + of the entity-body, in decimal number of OCTETs, sent to the + recipient or, in the case of the HEAD method, the size of the + entity-body that would have been sent had the request been a + GET.""", + ) + content_location = header_property[str]( + "Content-Location", + doc="""The Content-Location entity-header field MAY be used to + supply the resource location for the entity enclosed in the + message when that entity is accessible from a location separate + from the requested resource's URI.""", + ) + content_encoding = header_property[str]( + "Content-Encoding", + doc="""The Content-Encoding entity-header field is used as a + modifier to the media-type. When present, its value indicates + what additional content codings have been applied to the + entity-body, and thus what decoding mechanisms must be applied + in order to obtain the media-type referenced by the Content-Type + header field.""", + ) + content_md5 = header_property[str]( + "Content-MD5", + doc="""The Content-MD5 entity-header field, as defined in + RFC 1864, is an MD5 digest of the entity-body for the purpose of + providing an end-to-end message integrity check (MIC) of the + entity-body. (Note: a MIC is good for detecting accidental + modification of the entity-body in transit, but is not proof + against malicious attacks.)""", + ) + date = header_property( + "Date", + None, + parse_date, + http_date, + doc="""The Date general-header field represents the date and + time at which the message was originated, having the same + semantics as orig-date in RFC 822. + + .. versionchanged:: 2.0 + The datetime object is timezone-aware. + """, + ) + expires = header_property( + "Expires", + None, + parse_date, + http_date, + doc="""The Expires entity-header field gives the date/time after + which the response is considered stale. A stale cache entry may + not normally be returned by a cache. + + .. versionchanged:: 2.0 + The datetime object is timezone-aware. + """, + ) + last_modified = header_property( + "Last-Modified", + None, + parse_date, + http_date, + doc="""The Last-Modified entity-header field indicates the date + and time at which the origin server believes the variant was + last modified. + + .. versionchanged:: 2.0 + The datetime object is timezone-aware. + """, + ) + + @property + def retry_after(self) -> t.Optional[datetime]: + """The Retry-After response-header field can be used with a + 503 (Service Unavailable) response to indicate how long the + service is expected to be unavailable to the requesting client. + + Time in seconds until expiration or date. + + .. versionchanged:: 2.0 + The datetime object is timezone-aware. + """ + value = self.headers.get("retry-after") + if value is None: + return None + elif value.isdigit(): + return datetime.now(timezone.utc) + timedelta(seconds=int(value)) + return parse_date(value) + + @retry_after.setter + def retry_after(self, value: t.Optional[t.Union[datetime, int, str]]) -> None: + if value is None: + if "retry-after" in self.headers: + del self.headers["retry-after"] + return + elif isinstance(value, datetime): + value = http_date(value) + else: + value = str(value) + self.headers["Retry-After"] = value + + vary = _set_property( + "Vary", + doc="""The Vary field value indicates the set of request-header + fields that fully determines, while the response is fresh, + whether a cache is permitted to use the response to reply to a + subsequent request without revalidation.""", + ) + content_language = _set_property( + "Content-Language", + doc="""The Content-Language entity-header field describes the + natural language(s) of the intended audience for the enclosed + entity. Note that this might not be equivalent to all the + languages used within the entity-body.""", + ) + allow = _set_property( + "Allow", + doc="""The Allow entity-header field lists the set of methods + supported by the resource identified by the Request-URI. The + purpose of this field is strictly to inform the recipient of + valid methods associated with the resource. An Allow header + field MUST be present in a 405 (Method Not Allowed) + response.""", + ) + + # ETag + + @property + def cache_control(self) -> ResponseCacheControl: + """The Cache-Control general-header field is used to specify + directives that MUST be obeyed by all caching mechanisms along the + request/response chain. + """ + + def on_update(cache_control: ResponseCacheControl) -> None: + if not cache_control and "cache-control" in self.headers: + del self.headers["cache-control"] + elif cache_control: + self.headers["Cache-Control"] = cache_control.to_header() + + return parse_cache_control_header( + self.headers.get("cache-control"), on_update, ResponseCacheControl + ) + + def set_etag(self, etag: str, weak: bool = False) -> None: + """Set the etag, and override the old one if there was one.""" + self.headers["ETag"] = quote_etag(etag, weak) + + def get_etag(self) -> t.Union[t.Tuple[str, bool], t.Tuple[None, None]]: + """Return a tuple in the form ``(etag, is_weak)``. If there is no + ETag the return value is ``(None, None)``. + """ + return unquote_etag(self.headers.get("ETag")) + + accept_ranges = header_property[str]( + "Accept-Ranges", + doc="""The `Accept-Ranges` header. Even though the name would + indicate that multiple values are supported, it must be one + string token only. + + The values ``'bytes'`` and ``'none'`` are common. + + .. versionadded:: 0.7""", + ) + + @property + def content_range(self) -> ContentRange: + """The ``Content-Range`` header as a + :class:`~werkzeug.datastructures.ContentRange` object. Available + even if the header is not set. + + .. versionadded:: 0.7 + """ + + def on_update(rng: ContentRange) -> None: + if not rng: + del self.headers["content-range"] + else: + self.headers["Content-Range"] = rng.to_header() + + rv = parse_content_range_header(self.headers.get("content-range"), on_update) + # always provide a content range object to make the descriptor + # more user friendly. It provides an unset() method that can be + # used to remove the header quickly. + if rv is None: + rv = ContentRange(None, None, None, on_update=on_update) + return rv + + @content_range.setter + def content_range(self, value: t.Optional[t.Union[ContentRange, str]]) -> None: + if not value: + del self.headers["content-range"] + elif isinstance(value, str): + self.headers["Content-Range"] = value + else: + self.headers["Content-Range"] = value.to_header() + + # Authorization + + @property + def www_authenticate(self) -> WWWAuthenticate: + """The ``WWW-Authenticate`` header in a parsed form.""" + + def on_update(www_auth: WWWAuthenticate) -> None: + if not www_auth and "www-authenticate" in self.headers: + del self.headers["www-authenticate"] + elif www_auth: + self.headers["WWW-Authenticate"] = www_auth.to_header() + + header = self.headers.get("www-authenticate") + return parse_www_authenticate_header(header, on_update) + + # CSP + + content_security_policy = header_property( + "Content-Security-Policy", + None, + parse_csp_header, # type: ignore + dump_csp_header, + doc="""The Content-Security-Policy header adds an additional layer of + security to help detect and mitigate certain types of attacks.""", + ) + content_security_policy_report_only = header_property( + "Content-Security-Policy-Report-Only", + None, + parse_csp_header, # type: ignore + dump_csp_header, + doc="""The Content-Security-Policy-Report-Only header adds a csp policy + that is not enforced but is reported thereby helping detect + certain types of attacks.""", + ) + + # CORS + + @property + def access_control_allow_credentials(self) -> bool: + """Whether credentials can be shared by the browser to + JavaScript code. As part of the preflight request it indicates + whether credentials can be used on the cross origin request. + """ + return "Access-Control-Allow-Credentials" in self.headers + + @access_control_allow_credentials.setter + def access_control_allow_credentials(self, value: t.Optional[bool]) -> None: + if value is True: + self.headers["Access-Control-Allow-Credentials"] = "true" + else: + self.headers.pop("Access-Control-Allow-Credentials", None) + + access_control_allow_headers = header_property( + "Access-Control-Allow-Headers", + load_func=parse_set_header, + dump_func=dump_header, + doc="Which headers can be sent with the cross origin request.", + ) + + access_control_allow_methods = header_property( + "Access-Control-Allow-Methods", + load_func=parse_set_header, + dump_func=dump_header, + doc="Which methods can be used for the cross origin request.", + ) + + access_control_allow_origin = header_property[str]( + "Access-Control-Allow-Origin", + doc="The origin or '*' for any origin that may make cross origin requests.", + ) + + access_control_expose_headers = header_property( + "Access-Control-Expose-Headers", + load_func=parse_set_header, + dump_func=dump_header, + doc="Which headers can be shared by the browser to JavaScript code.", + ) + + access_control_max_age = header_property( + "Access-Control-Max-Age", + load_func=int, + dump_func=str, + doc="The maximum age in seconds the access control settings can be cached for.", + ) + + cross_origin_opener_policy = header_property[COOP]( + "Cross-Origin-Opener-Policy", + load_func=lambda value: COOP(value), + dump_func=lambda value: value.value, + default=COOP.UNSAFE_NONE, + doc="""Allows control over sharing of browsing context group with cross-origin + documents. Values must be a member of the :class:`werkzeug.http.COOP` enum.""", + ) + + cross_origin_embedder_policy = header_property[COEP]( + "Cross-Origin-Embedder-Policy", + load_func=lambda value: COEP(value), + dump_func=lambda value: value.value, + default=COEP.UNSAFE_NONE, + doc="""Prevents a document from loading any cross-origin resources that do not + explicitly grant the document permission. Values must be a member of the + :class:`werkzeug.http.COEP` enum.""", + ) diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/sansio/utils.py b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/sansio/utils.py new file mode 100644 index 000000000..1b4d8920c --- /dev/null +++ b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/sansio/utils.py @@ -0,0 +1,142 @@ +import typing as t + +from .._internal import _encode_idna +from ..exceptions import SecurityError +from ..urls import uri_to_iri +from ..urls import url_quote + + +def host_is_trusted(hostname: str, trusted_list: t.Iterable[str]) -> bool: + """Check if a host matches a list of trusted names. + + :param hostname: The name to check. + :param trusted_list: A list of valid names to match. If a name + starts with a dot it will match all subdomains. + + .. versionadded:: 0.9 + """ + if not hostname: + return False + + if isinstance(trusted_list, str): + trusted_list = [trusted_list] + + def _normalize(hostname: str) -> bytes: + if ":" in hostname: + hostname = hostname.rsplit(":", 1)[0] + + return _encode_idna(hostname) + + try: + hostname_bytes = _normalize(hostname) + except UnicodeError: + return False + + for ref in trusted_list: + if ref.startswith("."): + ref = ref[1:] + suffix_match = True + else: + suffix_match = False + + try: + ref_bytes = _normalize(ref) + except UnicodeError: + return False + + if ref_bytes == hostname_bytes: + return True + + if suffix_match and hostname_bytes.endswith(b"." + ref_bytes): + return True + + return False + + +def get_host( + scheme: str, + host_header: t.Optional[str], + server: t.Optional[t.Tuple[str, t.Optional[int]]] = None, + trusted_hosts: t.Optional[t.Iterable[str]] = None, +) -> str: + """Return the host for the given parameters. + + This first checks the ``host_header``. If it's not present, then + ``server`` is used. The host will only contain the port if it is + different than the standard port for the protocol. + + Optionally, verify that the host is trusted using + :func:`host_is_trusted` and raise a + :exc:`~werkzeug.exceptions.SecurityError` if it is not. + + :param scheme: The protocol the request used, like ``"https"``. + :param host_header: The ``Host`` header value. + :param server: Address of the server. ``(host, port)``, or + ``(path, None)`` for unix sockets. + :param trusted_hosts: A list of trusted host names. + + :return: Host, with port if necessary. + :raise ~werkzeug.exceptions.SecurityError: If the host is not + trusted. + """ + host = "" + + if host_header is not None: + host = host_header + elif server is not None: + host = server[0] + + if server[1] is not None: + host = f"{host}:{server[1]}" + + if scheme in {"http", "ws"} and host.endswith(":80"): + host = host[:-3] + elif scheme in {"https", "wss"} and host.endswith(":443"): + host = host[:-4] + + if trusted_hosts is not None: + if not host_is_trusted(host, trusted_hosts): + raise SecurityError(f"Host {host!r} is not trusted.") + + return host + + +def get_current_url( + scheme: str, + host: str, + root_path: t.Optional[str] = None, + path: t.Optional[str] = None, + query_string: t.Optional[bytes] = None, +) -> str: + """Recreate the URL for a request. If an optional part isn't + provided, it and subsequent parts are not included in the URL. + + The URL is an IRI, not a URI, so it may contain Unicode characters. + Use :func:`~werkzeug.urls.iri_to_uri` to convert it to ASCII. + + :param scheme: The protocol the request used, like ``"https"``. + :param host: The host the request was made to. See :func:`get_host`. + :param root_path: Prefix that the application is mounted under. This + is prepended to ``path``. + :param path: The path part of the URL after ``root_path``. + :param query_string: The portion of the URL after the "?". + """ + url = [scheme, "://", host] + + if root_path is None: + url.append("/") + return uri_to_iri("".join(url)) + + url.append(url_quote(root_path.rstrip("/"))) + url.append("/") + + if path is None: + return uri_to_iri("".join(url)) + + url.append(url_quote(path.lstrip("/"))) + + if query_string: + url.append("?") + url.append(url_quote(query_string, safe=":&%=+$!*'(),")) + + return uri_to_iri("".join(url)) diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/security.py b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/security.py new file mode 100644 index 000000000..e23040af9 --- /dev/null +++ b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/security.py @@ -0,0 +1,247 @@ +import hashlib +import hmac +import os +import posixpath +import secrets +import typing as t +import warnings + +if t.TYPE_CHECKING: + pass + +SALT_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" +DEFAULT_PBKDF2_ITERATIONS = 260000 + +_os_alt_seps: t.List[str] = list( + sep for sep in [os.path.sep, os.path.altsep] if sep is not None and sep != "/" +) + + +def pbkdf2_hex( + data: t.Union[str, bytes], + salt: t.Union[str, bytes], + iterations: int = DEFAULT_PBKDF2_ITERATIONS, + keylen: t.Optional[int] = None, + hashfunc: t.Optional[t.Union[str, t.Callable]] = None, +) -> str: + """Like :func:`pbkdf2_bin`, but returns a hex-encoded string. + + :param data: the data to derive. + :param salt: the salt for the derivation. + :param iterations: the number of iterations. + :param keylen: the length of the resulting key. If not provided, + the digest size will be used. + :param hashfunc: the hash function to use. This can either be the + string name of a known hash function, or a function + from the hashlib module. Defaults to sha256. + + .. deprecated:: 2.0 + Will be removed in Werkzeug 2.1. Use :func:`hashlib.pbkdf2_hmac` + instead. + + .. versionadded:: 0.9 + """ + warnings.warn( + "'pbkdf2_hex' is deprecated and will be removed in Werkzeug" + " 2.1. Use 'hashlib.pbkdf2_hmac().hex()' instead.", + DeprecationWarning, + stacklevel=2, + ) + return pbkdf2_bin(data, salt, iterations, keylen, hashfunc).hex() + + +def pbkdf2_bin( + data: t.Union[str, bytes], + salt: t.Union[str, bytes], + iterations: int = DEFAULT_PBKDF2_ITERATIONS, + keylen: t.Optional[int] = None, + hashfunc: t.Optional[t.Union[str, t.Callable]] = None, +) -> bytes: + """Returns a binary digest for the PBKDF2 hash algorithm of `data` + with the given `salt`. It iterates `iterations` times and produces a + key of `keylen` bytes. By default, SHA-256 is used as hash function; + a different hashlib `hashfunc` can be provided. + + :param data: the data to derive. + :param salt: the salt for the derivation. + :param iterations: the number of iterations. + :param keylen: the length of the resulting key. If not provided + the digest size will be used. + :param hashfunc: the hash function to use. This can either be the + string name of a known hash function or a function + from the hashlib module. Defaults to sha256. + + .. deprecated:: 2.0 + Will be removed in Werkzeug 2.1. Use :func:`hashlib.pbkdf2_hmac` + instead. + + .. versionadded:: 0.9 + """ + warnings.warn( + "'pbkdf2_bin' is deprecated and will be removed in Werkzeug" + " 2.1. Use 'hashlib.pbkdf2_hmac()' instead.", + DeprecationWarning, + stacklevel=2, + ) + + if isinstance(data, str): + data = data.encode("utf8") + + if isinstance(salt, str): + salt = salt.encode("utf8") + + if not hashfunc: + hash_name = "sha256" + elif callable(hashfunc): + hash_name = hashfunc().name + else: + hash_name = hashfunc + + return hashlib.pbkdf2_hmac(hash_name, data, salt, iterations, keylen) + + +def safe_str_cmp(a: str, b: str) -> bool: + """This function compares strings in somewhat constant time. This + requires that the length of at least one string is known in advance. + + Returns `True` if the two strings are equal, or `False` if they are not. + + .. deprecated:: 2.0 + Will be removed in Werkzeug 2.1. Use + :func:`hmac.compare_digest` instead. + + .. versionadded:: 0.7 + """ + warnings.warn( + "'safe_str_cmp' is deprecated and will be removed in Werkzeug" + " 2.1. Use 'hmac.compare_digest' instead.", + DeprecationWarning, + stacklevel=2, + ) + + if isinstance(a, str): + a = a.encode("utf-8") # type: ignore + + if isinstance(b, str): + b = b.encode("utf-8") # type: ignore + + return hmac.compare_digest(a, b) + + +def gen_salt(length: int) -> str: + """Generate a random string of SALT_CHARS with specified ``length``.""" + if length <= 0: + raise ValueError("Salt length must be positive") + + return "".join(secrets.choice(SALT_CHARS) for _ in range(length)) + + +def _hash_internal(method: str, salt: str, password: str) -> t.Tuple[str, str]: + """Internal password hash helper. Supports plaintext without salt, + unsalted and salted passwords. In case salted passwords are used + hmac is used. + """ + if method == "plain": + return password, method + + salt = salt.encode("utf-8") + password = password.encode("utf-8") + + if method.startswith("pbkdf2:"): + if not salt: + raise ValueError("Salt is required for PBKDF2") + + args = method[7:].split(":") + + if len(args) not in (1, 2): + raise ValueError("Invalid number of arguments for PBKDF2") + + method = args.pop(0) + iterations = int(args[0] or 0) if args else DEFAULT_PBKDF2_ITERATIONS + return ( + hashlib.pbkdf2_hmac(method, password, salt, iterations).hex(), + f"pbkdf2:{method}:{iterations}", + ) + + if salt: + return hmac.new(salt, password, method).hexdigest(), method + + return hashlib.new(method, password).hexdigest(), method + + +def generate_password_hash( + password: str, method: str = "pbkdf2:sha256", salt_length: int = 16 +) -> str: + """Hash a password with the given method and salt with a string of + the given length. The format of the string returned includes the method + that was used so that :func:`check_password_hash` can check the hash. + + The format for the hashed string looks like this:: + + method$salt$hash + + This method can **not** generate unsalted passwords but it is possible + to set param method='plain' in order to enforce plaintext passwords. + If a salt is used, hmac is used internally to salt the password. + + If PBKDF2 is wanted it can be enabled by setting the method to + ``pbkdf2:method:iterations`` where iterations is optional:: + + pbkdf2:sha256:80000$salt$hash + pbkdf2:sha256$salt$hash + + :param password: the password to hash. + :param method: the hash method to use (one that hashlib supports). Can + optionally be in the format ``pbkdf2:method:iterations`` + to enable PBKDF2. + :param salt_length: the length of the salt in letters. + """ + salt = gen_salt(salt_length) if method != "plain" else "" + h, actual_method = _hash_internal(method, salt, password) + return f"{actual_method}${salt}${h}" + + +def check_password_hash(pwhash: str, password: str) -> bool: + """Check a password against a given salted and hashed password value. + In order to support unsalted legacy passwords this method supports + plain text passwords, md5 and sha1 hashes (both salted and unsalted). + + Returns `True` if the password matched, `False` otherwise. + + :param pwhash: a hashed string like returned by + :func:`generate_password_hash`. + :param password: the plaintext password to compare against the hash. + """ + if pwhash.count("$") < 2: + return False + + method, salt, hashval = pwhash.split("$", 2) + return hmac.compare_digest(_hash_internal(method, salt, password)[0], hashval) + + +def safe_join(directory: str, *pathnames: str) -> t.Optional[str]: + """Safely join zero or more untrusted path components to a base + directory to avoid escaping the base directory. + + :param directory: The trusted base directory. + :param pathnames: The untrusted path components relative to the + base directory. + :return: A safe path, otherwise ``None``. + """ + parts = [directory] + + for filename in pathnames: + if filename != "": + filename = posixpath.normpath(filename) + + if ( + any(sep in filename for sep in _os_alt_seps) + or os.path.isabs(filename) + or filename == ".." + or filename.startswith("../") + ): + return None + + parts.append(filename) + + return posixpath.join(*parts) diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/serving.py b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/serving.py new file mode 100644 index 000000000..1be994929 --- /dev/null +++ b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/serving.py @@ -0,0 +1,1079 @@ +"""A WSGI and HTTP server for use **during development only**. This +server is convenient to use, but is not designed to be particularly +stable, secure, or efficient. Use a dedicate WSGI server and HTTP +server when deploying to production. + +It provides features like interactive debugging and code reloading. Use +``run_simple`` to start the server. Put this in a ``run.py`` script: + +.. code-block:: python + + from myapp import create_app + from werkzeug import run_simple +""" +import io +import os +import platform +import signal +import socket +import socketserver +import sys +import typing as t +import warnings +from datetime import datetime as dt +from datetime import timedelta +from datetime import timezone +from http.server import BaseHTTPRequestHandler +from http.server import HTTPServer + +from ._internal import _log +from ._internal import _wsgi_encoding_dance +from .exceptions import InternalServerError +from .urls import uri_to_iri +from .urls import url_parse +from .urls import url_unquote + +try: + import ssl +except ImportError: + + class _SslDummy: + def __getattr__(self, name: str) -> t.Any: + raise RuntimeError("SSL support unavailable") + + ssl = _SslDummy() # type: ignore + +_log_add_style = True + +if os.name == "nt": + try: + __import__("colorama") + except ImportError: + _log_add_style = False + +can_fork = hasattr(os, "fork") + +if can_fork: + ForkingMixIn = socketserver.ForkingMixIn +else: + + class ForkingMixIn: # type: ignore + pass + + +try: + af_unix = socket.AF_UNIX +except AttributeError: + af_unix = None # type: ignore + +LISTEN_QUEUE = 128 +can_open_by_fd = not platform.system() == "Windows" and hasattr(socket, "fromfd") + +_TSSLContextArg = t.Optional[ + t.Union["ssl.SSLContext", t.Tuple[str, t.Optional[str]], "te.Literal['adhoc']"] +] + +if t.TYPE_CHECKING: + import typing_extensions as te # noqa: F401 + from _typeshed.wsgi import WSGIApplication + from _typeshed.wsgi import WSGIEnvironment + from cryptography.hazmat.primitives.asymmetric.rsa import ( + RSAPrivateKeyWithSerialization, + ) + from cryptography.x509 import Certificate + + +class DechunkedInput(io.RawIOBase): + """An input stream that handles Transfer-Encoding 'chunked'""" + + def __init__(self, rfile: t.BinaryIO) -> None: + self._rfile = rfile + self._done = False + self._len = 0 + + def readable(self) -> bool: + return True + + def read_chunk_len(self) -> int: + try: + line = self._rfile.readline().decode("latin1") + _len = int(line.strip(), 16) + except ValueError: + raise OSError("Invalid chunk header") + if _len < 0: + raise OSError("Negative chunk length not allowed") + return _len + + def readinto(self, buf: bytearray) -> int: # type: ignore + read = 0 + while not self._done and read < len(buf): + if self._len == 0: + # This is the first chunk or we fully consumed the previous + # one. Read the next length of the next chunk + self._len = self.read_chunk_len() + + if self._len == 0: + # Found the final chunk of size 0. The stream is now exhausted, + # but there is still a final newline that should be consumed + self._done = True + + if self._len > 0: + # There is data (left) in this chunk, so append it to the + # buffer. If this operation fully consumes the chunk, this will + # reset self._len to 0. + n = min(len(buf), self._len) + + # If (read + chunk size) becomes more than len(buf), buf will + # grow beyond the original size and read more data than + # required. So only read as much data as can fit in buf. + if read + n > len(buf): + buf[read:] = self._rfile.read(len(buf) - read) + self._len -= len(buf) - read + read = len(buf) + else: + buf[read : read + n] = self._rfile.read(n) + self._len -= n + read += n + + if self._len == 0: + # Skip the terminating newline of a chunk that has been fully + # consumed. This also applies to the 0-sized final chunk + terminator = self._rfile.readline() + if terminator not in (b"\n", b"\r\n", b"\r"): + raise OSError("Missing chunk terminating newline") + + return read + + +class WSGIRequestHandler(BaseHTTPRequestHandler): + """A request handler that implements WSGI dispatching.""" + + server: "BaseWSGIServer" + + @property + def server_version(self) -> str: # type: ignore + from . import __version__ + + return f"Werkzeug/{__version__}" + + def make_environ(self) -> "WSGIEnvironment": + request_url = url_parse(self.path) + + def shutdown_server() -> None: + warnings.warn( + "The 'environ['werkzeug.server.shutdown']' function is" + " deprecated and will be removed in Werkzeug 2.1.", + stacklevel=2, + ) + self.server.shutdown_signal = True + + url_scheme = "http" if self.server.ssl_context is None else "https" + + if not self.client_address: + self.client_address = ("", 0) + elif isinstance(self.client_address, str): + self.client_address = (self.client_address, 0) + + # If there was no scheme but the path started with two slashes, + # the first segment may have been incorrectly parsed as the + # netloc, prepend it to the path again. + if not request_url.scheme and request_url.netloc: + path_info = f"/{request_url.netloc}{request_url.path}" + else: + path_info = request_url.path + + path_info = url_unquote(path_info) + + environ: "WSGIEnvironment" = { + "wsgi.version": (1, 0), + "wsgi.url_scheme": url_scheme, + "wsgi.input": self.rfile, + "wsgi.errors": sys.stderr, + "wsgi.multithread": self.server.multithread, + "wsgi.multiprocess": self.server.multiprocess, + "wsgi.run_once": False, + "werkzeug.server.shutdown": shutdown_server, + "werkzeug.socket": self.connection, + "SERVER_SOFTWARE": self.server_version, + "REQUEST_METHOD": self.command, + "SCRIPT_NAME": "", + "PATH_INFO": _wsgi_encoding_dance(path_info), + "QUERY_STRING": _wsgi_encoding_dance(request_url.query), + # Non-standard, added by mod_wsgi, uWSGI + "REQUEST_URI": _wsgi_encoding_dance(self.path), + # Non-standard, added by gunicorn + "RAW_URI": _wsgi_encoding_dance(self.path), + "REMOTE_ADDR": self.address_string(), + "REMOTE_PORT": self.port_integer(), + "SERVER_NAME": self.server.server_address[0], + "SERVER_PORT": str(self.server.server_address[1]), + "SERVER_PROTOCOL": self.request_version, + } + + for key, value in self.headers.items(): + key = key.upper().replace("-", "_") + value = value.replace("\r\n", "") + if key not in ("CONTENT_TYPE", "CONTENT_LENGTH"): + key = f"HTTP_{key}" + if key in environ: + value = f"{environ[key]},{value}" + environ[key] = value + + if environ.get("HTTP_TRANSFER_ENCODING", "").strip().lower() == "chunked": + environ["wsgi.input_terminated"] = True + environ["wsgi.input"] = DechunkedInput(environ["wsgi.input"]) + + # Per RFC 2616, if the URL is absolute, use that as the host. + # We're using "has a scheme" to indicate an absolute URL. + if request_url.scheme and request_url.netloc: + environ["HTTP_HOST"] = request_url.netloc + + try: + # binary_form=False gives nicer information, but wouldn't be compatible with + # what Nginx or Apache could return. + peer_cert = self.connection.getpeercert(binary_form=True) + if peer_cert is not None: + # Nginx and Apache use PEM format. + environ["SSL_CLIENT_CERT"] = ssl.DER_cert_to_PEM_cert(peer_cert) + except ValueError: + # SSL handshake hasn't finished. + self.server.log("error", "Cannot fetch SSL peer certificate info") + except AttributeError: + # Not using TLS, the socket will not have getpeercert(). + pass + + return environ + + def run_wsgi(self) -> None: + if self.headers.get("Expect", "").lower().strip() == "100-continue": + self.wfile.write(b"HTTP/1.1 100 Continue\r\n\r\n") + + self.environ = environ = self.make_environ() + status_set: t.Optional[str] = None + headers_set: t.Optional[t.List[t.Tuple[str, str]]] = None + status_sent: t.Optional[str] = None + headers_sent: t.Optional[t.List[t.Tuple[str, str]]] = None + + def write(data: bytes) -> None: + nonlocal status_sent, headers_sent + assert status_set is not None, "write() before start_response" + assert headers_set is not None, "write() before start_response" + if status_sent is None: + status_sent = status_set + headers_sent = headers_set + try: + code_str, msg = status_sent.split(None, 1) + except ValueError: + code_str, msg = status_sent, "" + code = int(code_str) + self.send_response(code, msg) + header_keys = set() + for key, value in headers_sent: + self.send_header(key, value) + key = key.lower() + header_keys.add(key) + if not ( + "content-length" in header_keys + or environ["REQUEST_METHOD"] == "HEAD" + or code < 200 + or code in (204, 304) + ): + self.close_connection = True + self.send_header("Connection", "close") + if "server" not in header_keys: + self.send_header("Server", self.version_string()) + if "date" not in header_keys: + self.send_header("Date", self.date_time_string()) + self.end_headers() + + assert isinstance(data, bytes), "applications must write bytes" + self.wfile.write(data) + self.wfile.flush() + + def start_response(status, headers, exc_info=None): # type: ignore + nonlocal status_set, headers_set + if exc_info: + try: + if headers_sent: + raise exc_info[1].with_traceback(exc_info[2]) + finally: + exc_info = None + elif headers_set: + raise AssertionError("Headers already set") + status_set = status + headers_set = headers + return write + + def execute(app: "WSGIApplication") -> None: + application_iter = app(environ, start_response) + try: + for data in application_iter: + write(data) + if not headers_sent: + write(b"") + finally: + if hasattr(application_iter, "close"): + application_iter.close() # type: ignore + + try: + execute(self.server.app) + except (ConnectionError, socket.timeout) as e: + self.connection_dropped(e, environ) + except Exception: + if self.server.passthrough_errors: + raise + from .debug.tbtools import get_current_traceback + + traceback = get_current_traceback(ignore_system_exceptions=True) + try: + # if we haven't yet sent the headers but they are set + # we roll back to be able to set them again. + if status_sent is None: + status_set = None + headers_set = None + execute(InternalServerError()) + except Exception: + pass + self.server.log("error", "Error on request:\n%s", traceback.plaintext) + + def handle(self) -> None: + """Handles a request ignoring dropped connections.""" + try: + BaseHTTPRequestHandler.handle(self) + except (ConnectionError, socket.timeout) as e: + self.connection_dropped(e) + except Exception as e: + if self.server.ssl_context is not None and is_ssl_error(e): + self.log_error("SSL error occurred: %s", e) + else: + raise + if self.server.shutdown_signal: + self.initiate_shutdown() + + def initiate_shutdown(self) -> None: + if is_running_from_reloader(): + # Windows does not provide SIGKILL, go with SIGTERM then. + sig = getattr(signal, "SIGKILL", signal.SIGTERM) + os.kill(os.getpid(), sig) + + self.server._BaseServer__shutdown_request = True # type: ignore + + def connection_dropped( + self, error: BaseException, environ: t.Optional["WSGIEnvironment"] = None + ) -> None: + """Called if the connection was closed by the client. By default + nothing happens. + """ + + def handle_one_request(self) -> None: + """Handle a single HTTP request.""" + self.raw_requestline = self.rfile.readline() + if not self.raw_requestline: + self.close_connection = True + elif self.parse_request(): + self.run_wsgi() + + def send_response(self, code: int, message: t.Optional[str] = None) -> None: + """Send the response header and log the response code.""" + self.log_request(code) + if message is None: + message = self.responses[code][0] if code in self.responses else "" + if self.request_version != "HTTP/0.9": + hdr = f"{self.protocol_version} {code} {message}\r\n" + self.wfile.write(hdr.encode("ascii")) + + def version_string(self) -> str: + return super().version_string().strip() + + def address_string(self) -> str: + if getattr(self, "environ", None): + return self.environ["REMOTE_ADDR"] # type: ignore + + if not self.client_address: + return "" + + return self.client_address[0] + + def port_integer(self) -> int: + return self.client_address[1] + + def log_request( + self, code: t.Union[int, str] = "-", size: t.Union[int, str] = "-" + ) -> None: + try: + path = uri_to_iri(self.path) + msg = f"{self.command} {path} {self.request_version}" + except AttributeError: + # path isn't set if the requestline was bad + msg = self.requestline + + code = str(code) + + if _log_add_style: + if code[0] == "1": # 1xx - Informational + msg = _ansi_style(msg, "bold") + elif code == "200": # 2xx - Success + pass + elif code == "304": # 304 - Resource Not Modified + msg = _ansi_style(msg, "cyan") + elif code[0] == "3": # 3xx - Redirection + msg = _ansi_style(msg, "green") + elif code == "404": # 404 - Resource Not Found + msg = _ansi_style(msg, "yellow") + elif code[0] == "4": # 4xx - Client Error + msg = _ansi_style(msg, "bold", "red") + else: # 5xx, or any other response + msg = _ansi_style(msg, "bold", "magenta") + + self.log("info", '"%s" %s %s', msg, code, size) + + def log_error(self, format: str, *args: t.Any) -> None: + self.log("error", format, *args) + + def log_message(self, format: str, *args: t.Any) -> None: + self.log("info", format, *args) + + def log(self, type: str, message: str, *args: t.Any) -> None: + _log( + type, + f"{self.address_string()} - - [{self.log_date_time_string()}] {message}\n", + *args, + ) + + +def _ansi_style(value: str, *styles: str) -> str: + codes = { + "bold": 1, + "red": 31, + "green": 32, + "yellow": 33, + "magenta": 35, + "cyan": 36, + } + + for style in styles: + value = f"\x1b[{codes[style]}m{value}" + + return f"{value}\x1b[0m" + + +def generate_adhoc_ssl_pair( + cn: t.Optional[str] = None, +) -> t.Tuple["Certificate", "RSAPrivateKeyWithSerialization"]: + try: + from cryptography import x509 + from cryptography.x509.oid import NameOID + from cryptography.hazmat.backends import default_backend + from cryptography.hazmat.primitives import hashes + from cryptography.hazmat.primitives.asymmetric import rsa + except ImportError: + raise TypeError("Using ad-hoc certificates requires the cryptography library.") + + backend = default_backend() + pkey = rsa.generate_private_key( + public_exponent=65537, key_size=2048, backend=backend + ) + + # pretty damn sure that this is not actually accepted by anyone + if cn is None: + cn = "*" + + subject = x509.Name( + [ + x509.NameAttribute(NameOID.ORGANIZATION_NAME, "Dummy Certificate"), + x509.NameAttribute(NameOID.COMMON_NAME, cn), + ] + ) + + backend = default_backend() + cert = ( + x509.CertificateBuilder() + .subject_name(subject) + .issuer_name(subject) + .public_key(pkey.public_key()) + .serial_number(x509.random_serial_number()) + .not_valid_before(dt.now(timezone.utc)) + .not_valid_after(dt.now(timezone.utc) + timedelta(days=365)) + .add_extension(x509.ExtendedKeyUsage([x509.OID_SERVER_AUTH]), critical=False) + .add_extension(x509.SubjectAlternativeName([x509.DNSName("*")]), critical=False) + .sign(pkey, hashes.SHA256(), backend) + ) + return cert, pkey + + +def make_ssl_devcert( + base_path: str, host: t.Optional[str] = None, cn: t.Optional[str] = None +) -> t.Tuple[str, str]: + """Creates an SSL key for development. This should be used instead of + the ``'adhoc'`` key which generates a new cert on each server start. + It accepts a path for where it should store the key and cert and + either a host or CN. If a host is given it will use the CN + ``*.host/CN=host``. + + For more information see :func:`run_simple`. + + .. versionadded:: 0.9 + + :param base_path: the path to the certificate and key. The extension + ``.crt`` is added for the certificate, ``.key`` is + added for the key. + :param host: the name of the host. This can be used as an alternative + for the `cn`. + :param cn: the `CN` to use. + """ + + if host is not None: + cn = f"*.{host}/CN={host}" + cert, pkey = generate_adhoc_ssl_pair(cn=cn) + + from cryptography.hazmat.primitives import serialization + + cert_file = f"{base_path}.crt" + pkey_file = f"{base_path}.key" + + with open(cert_file, "wb") as f: + f.write(cert.public_bytes(serialization.Encoding.PEM)) + with open(pkey_file, "wb") as f: + f.write( + pkey.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.TraditionalOpenSSL, + encryption_algorithm=serialization.NoEncryption(), + ) + ) + + return cert_file, pkey_file + + +def generate_adhoc_ssl_context() -> "ssl.SSLContext": + """Generates an adhoc SSL context for the development server.""" + import tempfile + import atexit + + cert, pkey = generate_adhoc_ssl_pair() + + from cryptography.hazmat.primitives import serialization + + cert_handle, cert_file = tempfile.mkstemp() + pkey_handle, pkey_file = tempfile.mkstemp() + atexit.register(os.remove, pkey_file) + atexit.register(os.remove, cert_file) + + os.write(cert_handle, cert.public_bytes(serialization.Encoding.PEM)) + os.write( + pkey_handle, + pkey.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.TraditionalOpenSSL, + encryption_algorithm=serialization.NoEncryption(), + ), + ) + + os.close(cert_handle) + os.close(pkey_handle) + ctx = load_ssl_context(cert_file, pkey_file) + return ctx + + +def load_ssl_context( + cert_file: str, pkey_file: t.Optional[str] = None, protocol: t.Optional[int] = None +) -> "ssl.SSLContext": + """Loads SSL context from cert/private key files and optional protocol. + Many parameters are directly taken from the API of + :py:class:`ssl.SSLContext`. + + :param cert_file: Path of the certificate to use. + :param pkey_file: Path of the private key to use. If not given, the key + will be obtained from the certificate file. + :param protocol: A ``PROTOCOL`` constant from the :mod:`ssl` module. + Defaults to :data:`ssl.PROTOCOL_TLS_SERVER`. + """ + if protocol is None: + protocol = ssl.PROTOCOL_TLS_SERVER + + ctx = ssl.SSLContext(protocol) + ctx.load_cert_chain(cert_file, pkey_file) + return ctx + + +def is_ssl_error(error: t.Optional[Exception] = None) -> bool: + """Checks if the given error (or the current one) is an SSL error.""" + if error is None: + error = t.cast(Exception, sys.exc_info()[1]) + return isinstance(error, ssl.SSLError) + + +def select_address_family(host: str, port: int) -> socket.AddressFamily: + """Return ``AF_INET4``, ``AF_INET6``, or ``AF_UNIX`` depending on + the host and port.""" + if host.startswith("unix://"): + return socket.AF_UNIX + elif ":" in host and hasattr(socket, "AF_INET6"): + return socket.AF_INET6 + return socket.AF_INET + + +def get_sockaddr( + host: str, port: int, family: socket.AddressFamily +) -> t.Union[t.Tuple[str, int], str]: + """Return a fully qualified socket address that can be passed to + :func:`socket.bind`.""" + if family == af_unix: + return host.split("://", 1)[1] + try: + res = socket.getaddrinfo( + host, port, family, socket.SOCK_STREAM, socket.IPPROTO_TCP + ) + except socket.gaierror: + return host, port + return res[0][4] # type: ignore + + +def get_interface_ip(family: socket.AddressFamily) -> str: + """Get the IP address of an external interface. Used when binding to + 0.0.0.0 or ::1 to show a more useful URL. + + :meta private: + """ + # arbitrary private address + host = "fd31:f903:5ab5:1::1" if family == socket.AF_INET6 else "10.253.155.219" + + with socket.socket(family, socket.SOCK_DGRAM) as s: + try: + s.connect((host, 58162)) + except OSError: + return "::1" if family == socket.AF_INET6 else "127.0.0.1" + + return s.getsockname()[0] # type: ignore + + +class BaseWSGIServer(HTTPServer): + + """Simple single-threaded, single-process WSGI server.""" + + multithread = False + multiprocess = False + request_queue_size = LISTEN_QUEUE + + def __init__( + self, + host: str, + port: int, + app: "WSGIApplication", + handler: t.Optional[t.Type[WSGIRequestHandler]] = None, + passthrough_errors: bool = False, + ssl_context: t.Optional[_TSSLContextArg] = None, + fd: t.Optional[int] = None, + ) -> None: + if handler is None: + handler = WSGIRequestHandler + + self.address_family = select_address_family(host, port) + + if fd is not None: + real_sock = socket.fromfd(fd, self.address_family, socket.SOCK_STREAM) + port = 0 + + server_address = get_sockaddr(host, int(port), self.address_family) + + # remove socket file if it already exists + if self.address_family == af_unix: + server_address = t.cast(str, server_address) + + if os.path.exists(server_address): + os.unlink(server_address) + + super().__init__(server_address, handler) # type: ignore + + self.app = app + self.passthrough_errors = passthrough_errors + self.shutdown_signal = False + self.host = host + self.port = self.socket.getsockname()[1] + + # Patch in the original socket. + if fd is not None: + self.socket.close() + self.socket = real_sock + self.server_address = self.socket.getsockname() + + if ssl_context is not None: + if isinstance(ssl_context, tuple): + ssl_context = load_ssl_context(*ssl_context) + if ssl_context == "adhoc": + ssl_context = generate_adhoc_ssl_context() + + self.socket = ssl_context.wrap_socket(self.socket, server_side=True) + self.ssl_context: t.Optional["ssl.SSLContext"] = ssl_context + else: + self.ssl_context = None + + def log(self, type: str, message: str, *args: t.Any) -> None: + _log(type, message, *args) + + def serve_forever(self, poll_interval: float = 0.5) -> None: + self.shutdown_signal = False + try: + super().serve_forever(poll_interval=poll_interval) + except KeyboardInterrupt: + pass + finally: + self.server_close() + + def handle_error(self, request: t.Any, client_address: t.Tuple[str, int]) -> None: + if self.passthrough_errors: + raise + + return super().handle_error(request, client_address) + + +class ThreadedWSGIServer(socketserver.ThreadingMixIn, BaseWSGIServer): + + """A WSGI server that does threading.""" + + multithread = True + daemon_threads = True + + +class ForkingWSGIServer(ForkingMixIn, BaseWSGIServer): + + """A WSGI server that does forking.""" + + multiprocess = True + + def __init__( + self, + host: str, + port: int, + app: "WSGIApplication", + processes: int = 40, + handler: t.Optional[t.Type[WSGIRequestHandler]] = None, + passthrough_errors: bool = False, + ssl_context: t.Optional[_TSSLContextArg] = None, + fd: t.Optional[int] = None, + ) -> None: + if not can_fork: + raise ValueError("Your platform does not support forking.") + BaseWSGIServer.__init__( + self, host, port, app, handler, passthrough_errors, ssl_context, fd + ) + self.max_children = processes + + +def make_server( + host: str, + port: int, + app: "WSGIApplication", + threaded: bool = False, + processes: int = 1, + request_handler: t.Optional[t.Type[WSGIRequestHandler]] = None, + passthrough_errors: bool = False, + ssl_context: t.Optional[_TSSLContextArg] = None, + fd: t.Optional[int] = None, +) -> BaseWSGIServer: + """Create a new server instance that is either threaded, or forks + or just processes one request after another. + """ + if threaded and processes > 1: + raise ValueError("cannot have a multithreaded and multi process server.") + elif threaded: + return ThreadedWSGIServer( + host, port, app, request_handler, passthrough_errors, ssl_context, fd=fd + ) + elif processes > 1: + return ForkingWSGIServer( + host, + port, + app, + processes, + request_handler, + passthrough_errors, + ssl_context, + fd=fd, + ) + else: + return BaseWSGIServer( + host, port, app, request_handler, passthrough_errors, ssl_context, fd=fd + ) + + +def is_running_from_reloader() -> bool: + """Checks if the application is running from within the Werkzeug + reloader subprocess. + + .. versionadded:: 0.10 + """ + return os.environ.get("WERKZEUG_RUN_MAIN") == "true" + + +def run_simple( + hostname: str, + port: int, + application: "WSGIApplication", + use_reloader: bool = False, + use_debugger: bool = False, + use_evalex: bool = True, + extra_files: t.Optional[t.Iterable[str]] = None, + exclude_patterns: t.Optional[t.Iterable[str]] = None, + reloader_interval: int = 1, + reloader_type: str = "auto", + threaded: bool = False, + processes: int = 1, + request_handler: t.Optional[t.Type[WSGIRequestHandler]] = None, + static_files: t.Optional[t.Dict[str, t.Union[str, t.Tuple[str, str]]]] = None, + passthrough_errors: bool = False, + ssl_context: t.Optional[_TSSLContextArg] = None, +) -> None: + """Start a WSGI application. Optional features include a reloader, + multithreading and fork support. + + This function has a command-line interface too:: + + python -m werkzeug.serving --help + + .. versionchanged:: 2.0 + Added ``exclude_patterns`` parameter. + + .. versionadded:: 0.5 + `static_files` was added to simplify serving of static files as well + as `passthrough_errors`. + + .. versionadded:: 0.6 + support for SSL was added. + + .. versionadded:: 0.8 + Added support for automatically loading a SSL context from certificate + file and private key. + + .. versionadded:: 0.9 + Added command-line interface. + + .. versionadded:: 0.10 + Improved the reloader and added support for changing the backend + through the `reloader_type` parameter. See :ref:`reloader` + for more information. + + .. versionchanged:: 0.15 + Bind to a Unix socket by passing a path that starts with + ``unix://`` as the ``hostname``. + + :param hostname: The host to bind to, for example ``'localhost'``. + If the value is a path that starts with ``unix://`` it will bind + to a Unix socket instead of a TCP socket.. + :param port: The port for the server. eg: ``8080`` + :param application: the WSGI application to execute + :param use_reloader: should the server automatically restart the python + process if modules were changed? + :param use_debugger: should the werkzeug debugging system be used? + :param use_evalex: should the exception evaluation feature be enabled? + :param extra_files: a list of files the reloader should watch + additionally to the modules. For example configuration + files. + :param exclude_patterns: List of :mod:`fnmatch` patterns to ignore + when running the reloader. For example, ignore cache files that + shouldn't reload when updated. + :param reloader_interval: the interval for the reloader in seconds. + :param reloader_type: the type of reloader to use. The default is + auto detection. Valid values are ``'stat'`` and + ``'watchdog'``. See :ref:`reloader` for more + information. + :param threaded: should the process handle each request in a separate + thread? + :param processes: if greater than 1 then handle each request in a new process + up to this maximum number of concurrent processes. + :param request_handler: optional parameter that can be used to replace + the default one. You can use this to replace it + with a different + :class:`~BaseHTTPServer.BaseHTTPRequestHandler` + subclass. + :param static_files: a list or dict of paths for static files. This works + exactly like :class:`SharedDataMiddleware`, it's actually + just wrapping the application in that middleware before + serving. + :param passthrough_errors: set this to `True` to disable the error catching. + This means that the server will die on errors but + it can be useful to hook debuggers in (pdb etc.) + :param ssl_context: an SSL context for the connection. Either an + :class:`ssl.SSLContext`, a tuple in the form + ``(cert_file, pkey_file)``, the string ``'adhoc'`` if + the server should automatically create one, or ``None`` + to disable SSL (which is the default). + """ + if not isinstance(port, int): + raise TypeError("port must be an integer") + if use_debugger: + from .debug import DebuggedApplication + + application = DebuggedApplication(application, use_evalex) + if static_files: + from .middleware.shared_data import SharedDataMiddleware + + application = SharedDataMiddleware(application, static_files) + + def log_startup(sock: socket.socket) -> None: + all_addresses_message = ( + " * Running on all addresses.\n" + " WARNING: This is a development server. Do not use it in" + " a production deployment." + ) + + if sock.family == af_unix: + _log("info", " * Running on %s (Press CTRL+C to quit)", hostname) + else: + if hostname == "0.0.0.0": + _log("warning", all_addresses_message) + display_hostname = get_interface_ip(socket.AF_INET) + elif hostname == "::": + _log("warning", all_addresses_message) + display_hostname = get_interface_ip(socket.AF_INET6) + else: + display_hostname = hostname + + if ":" in display_hostname: + display_hostname = f"[{display_hostname}]" + + _log( + "info", + " * Running on %s://%s:%d/ (Press CTRL+C to quit)", + "http" if ssl_context is None else "https", + display_hostname, + sock.getsockname()[1], + ) + + def inner() -> None: + try: + fd: t.Optional[int] = int(os.environ["WERKZEUG_SERVER_FD"]) + except (LookupError, ValueError): + fd = None + srv = make_server( + hostname, + port, + application, + threaded, + processes, + request_handler, + passthrough_errors, + ssl_context, + fd=fd, + ) + if fd is None: + log_startup(srv.socket) + srv.serve_forever() + + if use_reloader: + # If we're not running already in the subprocess that is the + # reloader we want to open up a socket early to make sure the + # port is actually available. + if not is_running_from_reloader(): + if port == 0 and not can_open_by_fd: + raise ValueError( + "Cannot bind to a random port with enabled " + "reloader if the Python interpreter does " + "not support socket opening by fd." + ) + + # Create and destroy a socket so that any exceptions are + # raised before we spawn a separate Python interpreter and + # lose this ability. + address_family = select_address_family(hostname, port) + server_address = get_sockaddr(hostname, port, address_family) + s = socket.socket(address_family, socket.SOCK_STREAM) + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + s.bind(server_address) + s.set_inheritable(True) + + # If we can open the socket by file descriptor, then we can just + # reuse this one and our socket will survive the restarts. + if can_open_by_fd: + os.environ["WERKZEUG_SERVER_FD"] = str(s.fileno()) + s.listen(LISTEN_QUEUE) + log_startup(s) + else: + s.close() + if address_family == af_unix: + server_address = t.cast(str, server_address) + _log("info", "Unlinking %s", server_address) + os.unlink(server_address) + + from ._reloader import run_with_reloader as _rwr + + _rwr( + inner, + extra_files=extra_files, + exclude_patterns=exclude_patterns, + interval=reloader_interval, + reloader_type=reloader_type, + ) + else: + inner() + + +def run_with_reloader(*args: t.Any, **kwargs: t.Any) -> None: + """Run a process with the reloader. This is not a public API, do + not use this function. + + .. deprecated:: 2.0 + Will be removed in Werkzeug 2.1. + """ + from ._reloader import run_with_reloader as _rwr + + warnings.warn( + ( + "'run_with_reloader' is a private API, it will no longer be" + " accessible in Werkzeug 2.1. Use 'run_simple' instead." + ), + DeprecationWarning, + stacklevel=2, + ) + _rwr(*args, **kwargs) + + +def main() -> None: + """A simple command-line interface for :py:func:`run_simple`.""" + import argparse + from .utils import import_string + + _log("warning", "This CLI is deprecated and will be removed in version 2.1.") + + parser = argparse.ArgumentParser( + description="Run the given WSGI application with the development server.", + allow_abbrev=False, + ) + parser.add_argument( + "-b", + "--bind", + dest="address", + help="The hostname:port the app should listen on.", + ) + parser.add_argument( + "-d", + "--debug", + action="store_true", + help="Show the interactive debugger for unhandled exceptions.", + ) + parser.add_argument( + "-r", + "--reload", + action="store_true", + help="Reload the process if modules change.", + ) + parser.add_argument( + "application", help="Application to import and serve, in the form module:app." + ) + args = parser.parse_args() + hostname, port = None, None + + if args.address: + hostname, _, port = args.address.partition(":") + + run_simple( + hostname=hostname or "127.0.0.1", + port=int(port or 5000), + application=import_string(args.application), + use_reloader=args.reload, + use_debugger=args.debug, + ) + + +if __name__ == "__main__": + main() diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/test.py b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/test.py new file mode 100644 index 000000000..9301c02fd --- /dev/null +++ b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/test.py @@ -0,0 +1,1324 @@ +import mimetypes +import sys +import typing as t +import warnings +from collections import defaultdict +from datetime import datetime +from datetime import timedelta +from http.cookiejar import CookieJar +from io import BytesIO +from itertools import chain +from random import random +from tempfile import TemporaryFile +from time import time +from urllib.request import Request as _UrllibRequest + +from ._internal import _get_environ +from ._internal import _make_encode_wrapper +from ._internal import _wsgi_decoding_dance +from ._internal import _wsgi_encoding_dance +from .datastructures import Authorization +from .datastructures import CallbackDict +from .datastructures import CombinedMultiDict +from .datastructures import EnvironHeaders +from .datastructures import FileMultiDict +from .datastructures import Headers +from .datastructures import MultiDict +from .http import dump_cookie +from .http import dump_options_header +from .http import parse_options_header +from .sansio.multipart import Data +from .sansio.multipart import Epilogue +from .sansio.multipart import Field +from .sansio.multipart import File +from .sansio.multipart import MultipartEncoder +from .sansio.multipart import Preamble +from .urls import iri_to_uri +from .urls import url_encode +from .urls import url_fix +from .urls import url_parse +from .urls import url_unparse +from .urls import url_unquote +from .utils import get_content_type +from .wrappers.request import Request +from .wrappers.response import Response +from .wsgi import ClosingIterator +from .wsgi import get_current_url + +if t.TYPE_CHECKING: + from _typeshed.wsgi import WSGIApplication + from _typeshed.wsgi import WSGIEnvironment + + +def stream_encode_multipart( + data: t.Mapping[str, t.Any], + use_tempfile: bool = True, + threshold: int = 1024 * 500, + boundary: t.Optional[str] = None, + charset: str = "utf-8", +) -> t.Tuple[t.BinaryIO, int, str]: + """Encode a dict of values (either strings or file descriptors or + :class:`FileStorage` objects.) into a multipart encoded string stored + in a file descriptor. + """ + if boundary is None: + boundary = f"---------------WerkzeugFormPart_{time()}{random()}" + + stream: t.BinaryIO = BytesIO() + total_length = 0 + on_disk = False + + if use_tempfile: + + def write_binary(s: bytes) -> int: + nonlocal stream, total_length, on_disk + + if on_disk: + return stream.write(s) + else: + length = len(s) + + if length + total_length <= threshold: + stream.write(s) + else: + new_stream = t.cast(t.BinaryIO, TemporaryFile("wb+")) + new_stream.write(stream.getvalue()) # type: ignore + new_stream.write(s) + stream = new_stream + on_disk = True + + total_length += length + return length + + else: + write_binary = stream.write + + encoder = MultipartEncoder(boundary.encode()) + write_binary(encoder.send_event(Preamble(data=b""))) + for key, value in _iter_data(data): + reader = getattr(value, "read", None) + if reader is not None: + filename = getattr(value, "filename", getattr(value, "name", None)) + content_type = getattr(value, "content_type", None) + if content_type is None: + content_type = ( + filename + and mimetypes.guess_type(filename)[0] + or "application/octet-stream" + ) + headers = Headers([("Content-Type", content_type)]) + if filename is None: + write_binary(encoder.send_event(Field(name=key, headers=headers))) + else: + write_binary( + encoder.send_event( + File(name=key, filename=filename, headers=headers) + ) + ) + while True: + chunk = reader(16384) + + if not chunk: + break + + write_binary(encoder.send_event(Data(data=chunk, more_data=True))) + else: + if not isinstance(value, str): + value = str(value) + write_binary(encoder.send_event(Field(name=key, headers=Headers()))) + write_binary( + encoder.send_event(Data(data=value.encode(charset), more_data=False)) + ) + + write_binary(encoder.send_event(Epilogue(data=b""))) + + length = stream.tell() + stream.seek(0) + return stream, length, boundary + + +def encode_multipart( + values: t.Mapping[str, t.Any], + boundary: t.Optional[str] = None, + charset: str = "utf-8", +) -> t.Tuple[str, bytes]: + """Like `stream_encode_multipart` but returns a tuple in the form + (``boundary``, ``data``) where data is bytes. + """ + stream, length, boundary = stream_encode_multipart( + values, use_tempfile=False, boundary=boundary, charset=charset + ) + return boundary, stream.read() + + +class _TestCookieHeaders: + """A headers adapter for cookielib""" + + def __init__(self, headers: t.Union[Headers, t.List[t.Tuple[str, str]]]) -> None: + self.headers = headers + + def getheaders(self, name: str) -> t.Iterable[str]: + headers = [] + name = name.lower() + for k, v in self.headers: + if k.lower() == name: + headers.append(v) + return headers + + def get_all( + self, name: str, default: t.Optional[t.Iterable[str]] = None + ) -> t.Iterable[str]: + headers = self.getheaders(name) + + if not headers: + return default # type: ignore + + return headers + + +class _TestCookieResponse: + """Something that looks like a httplib.HTTPResponse, but is actually just an + adapter for our test responses to make them available for cookielib. + """ + + def __init__(self, headers: t.Union[Headers, t.List[t.Tuple[str, str]]]) -> None: + self.headers = _TestCookieHeaders(headers) + + def info(self) -> _TestCookieHeaders: + return self.headers + + +class _TestCookieJar(CookieJar): + """A cookielib.CookieJar modified to inject and read cookie headers from + and to wsgi environments, and wsgi application responses. + """ + + def inject_wsgi(self, environ: "WSGIEnvironment") -> None: + """Inject the cookies as client headers into the server's wsgi + environment. + """ + cvals = [f"{c.name}={c.value}" for c in self] + + if cvals: + environ["HTTP_COOKIE"] = "; ".join(cvals) + else: + environ.pop("HTTP_COOKIE", None) + + def extract_wsgi( + self, + environ: "WSGIEnvironment", + headers: t.Union[Headers, t.List[t.Tuple[str, str]]], + ) -> None: + """Extract the server's set-cookie headers as cookies into the + cookie jar. + """ + self.extract_cookies( + _TestCookieResponse(headers), # type: ignore + _UrllibRequest(get_current_url(environ)), + ) + + +def _iter_data(data: t.Mapping[str, t.Any]) -> t.Iterator[t.Tuple[str, t.Any]]: + """Iterate over a mapping that might have a list of values, yielding + all key, value pairs. Almost like iter_multi_items but only allows + lists, not tuples, of values so tuples can be used for files. + """ + if isinstance(data, MultiDict): + yield from data.items(multi=True) + else: + for key, value in data.items(): + if isinstance(value, list): + for v in value: + yield key, v + else: + yield key, value + + +_TAnyMultiDict = t.TypeVar("_TAnyMultiDict", bound=MultiDict) + + +class EnvironBuilder: + """This class can be used to conveniently create a WSGI environment + for testing purposes. It can be used to quickly create WSGI environments + or request objects from arbitrary data. + + The signature of this class is also used in some other places as of + Werkzeug 0.5 (:func:`create_environ`, :meth:`Response.from_values`, + :meth:`Client.open`). Because of this most of the functionality is + available through the constructor alone. + + Files and regular form data can be manipulated independently of each + other with the :attr:`form` and :attr:`files` attributes, but are + passed with the same argument to the constructor: `data`. + + `data` can be any of these values: + + - a `str` or `bytes` object: The object is converted into an + :attr:`input_stream`, the :attr:`content_length` is set and you have to + provide a :attr:`content_type`. + - a `dict` or :class:`MultiDict`: The keys have to be strings. The values + have to be either any of the following objects, or a list of any of the + following objects: + + - a :class:`file`-like object: These are converted into + :class:`FileStorage` objects automatically. + - a `tuple`: The :meth:`~FileMultiDict.add_file` method is called + with the key and the unpacked `tuple` items as positional + arguments. + - a `str`: The string is set as form data for the associated key. + - a file-like object: The object content is loaded in memory and then + handled like a regular `str` or a `bytes`. + + :param path: the path of the request. In the WSGI environment this will + end up as `PATH_INFO`. If the `query_string` is not defined + and there is a question mark in the `path` everything after + it is used as query string. + :param base_url: the base URL is a URL that is used to extract the WSGI + URL scheme, host (server name + server port) and the + script root (`SCRIPT_NAME`). + :param query_string: an optional string or dict with URL parameters. + :param method: the HTTP method to use, defaults to `GET`. + :param input_stream: an optional input stream. Do not specify this and + `data`. As soon as an input stream is set you can't + modify :attr:`args` and :attr:`files` unless you + set the :attr:`input_stream` to `None` again. + :param content_type: The content type for the request. As of 0.5 you + don't have to provide this when specifying files + and form data via `data`. + :param content_length: The content length for the request. You don't + have to specify this when providing data via + `data`. + :param errors_stream: an optional error stream that is used for + `wsgi.errors`. Defaults to :data:`stderr`. + :param multithread: controls `wsgi.multithread`. Defaults to `False`. + :param multiprocess: controls `wsgi.multiprocess`. Defaults to `False`. + :param run_once: controls `wsgi.run_once`. Defaults to `False`. + :param headers: an optional list or :class:`Headers` object of headers. + :param data: a string or dict of form data or a file-object. + See explanation above. + :param json: An object to be serialized and assigned to ``data``. + Defaults the content type to ``"application/json"``. + Serialized with the function assigned to :attr:`json_dumps`. + :param environ_base: an optional dict of environment defaults. + :param environ_overrides: an optional dict of environment overrides. + :param charset: the charset used to encode string data. + :param auth: An authorization object to use for the + ``Authorization`` header value. A ``(username, password)`` tuple + is a shortcut for ``Basic`` authorization. + + .. versionchanged:: 2.0 + ``REQUEST_URI`` and ``RAW_URI`` is the full raw URI including + the query string, not only the path. + + .. versionchanged:: 2.0 + The default :attr:`request_class` is ``Request`` instead of + ``BaseRequest``. + + .. versionadded:: 2.0 + Added the ``auth`` parameter. + + .. versionadded:: 0.15 + The ``json`` param and :meth:`json_dumps` method. + + .. versionadded:: 0.15 + The environ has keys ``REQUEST_URI`` and ``RAW_URI`` containing + the path before perecent-decoding. This is not part of the WSGI + PEP, but many WSGI servers include it. + + .. versionchanged:: 0.6 + ``path`` and ``base_url`` can now be unicode strings that are + encoded with :func:`iri_to_uri`. + """ + + #: the server protocol to use. defaults to HTTP/1.1 + server_protocol = "HTTP/1.1" + + #: the wsgi version to use. defaults to (1, 0) + wsgi_version = (1, 0) + + #: The default request class used by :meth:`get_request`. + request_class = Request + + import json + + #: The serialization function used when ``json`` is passed. + json_dumps = staticmethod(json.dumps) + del json + + _args: t.Optional[MultiDict] + _query_string: t.Optional[str] + _input_stream: t.Optional[t.BinaryIO] + _form: t.Optional[MultiDict] + _files: t.Optional[FileMultiDict] + + def __init__( + self, + path: str = "/", + base_url: t.Optional[str] = None, + query_string: t.Optional[t.Union[t.Mapping[str, str], str]] = None, + method: str = "GET", + input_stream: t.Optional[t.BinaryIO] = None, + content_type: t.Optional[str] = None, + content_length: t.Optional[int] = None, + errors_stream: t.Optional[t.TextIO] = None, + multithread: bool = False, + multiprocess: bool = False, + run_once: bool = False, + headers: t.Optional[t.Union[Headers, t.Iterable[t.Tuple[str, str]]]] = None, + data: t.Optional[t.Union[t.BinaryIO, str, bytes, t.Mapping[str, t.Any]]] = None, + environ_base: t.Optional[t.Mapping[str, t.Any]] = None, + environ_overrides: t.Optional[t.Mapping[str, t.Any]] = None, + charset: str = "utf-8", + mimetype: t.Optional[str] = None, + json: t.Optional[t.Mapping[str, t.Any]] = None, + auth: t.Optional[t.Union[Authorization, t.Tuple[str, str]]] = None, + ) -> None: + path_s = _make_encode_wrapper(path) + if query_string is not None and path_s("?") in path: + raise ValueError("Query string is defined in the path and as an argument") + request_uri = url_parse(path) + if query_string is None and path_s("?") in path: + query_string = request_uri.query + self.charset = charset + self.path = iri_to_uri(request_uri.path) + self.request_uri = path + if base_url is not None: + base_url = url_fix(iri_to_uri(base_url, charset), charset) + self.base_url = base_url # type: ignore + if isinstance(query_string, (bytes, str)): + self.query_string = query_string + else: + if query_string is None: + query_string = MultiDict() + elif not isinstance(query_string, MultiDict): + query_string = MultiDict(query_string) + self.args = query_string + self.method = method + if headers is None: + headers = Headers() + elif not isinstance(headers, Headers): + headers = Headers(headers) + self.headers = headers + if content_type is not None: + self.content_type = content_type + if errors_stream is None: + errors_stream = sys.stderr + self.errors_stream = errors_stream + self.multithread = multithread + self.multiprocess = multiprocess + self.run_once = run_once + self.environ_base = environ_base + self.environ_overrides = environ_overrides + self.input_stream = input_stream + self.content_length = content_length + self.closed = False + + if auth is not None: + if isinstance(auth, tuple): + auth = Authorization( + "basic", {"username": auth[0], "password": auth[1]} + ) + + self.headers.set("Authorization", auth.to_header()) + + if json is not None: + if data is not None: + raise TypeError("can't provide both json and data") + + data = self.json_dumps(json) + + if self.content_type is None: + self.content_type = "application/json" + + if data: + if input_stream is not None: + raise TypeError("can't provide input stream and data") + if hasattr(data, "read"): + data = data.read() # type: ignore + if isinstance(data, str): + data = data.encode(self.charset) + if isinstance(data, bytes): + self.input_stream = BytesIO(data) + if self.content_length is None: + self.content_length = len(data) + else: + for key, value in _iter_data(data): # type: ignore + if isinstance(value, (tuple, dict)) or hasattr(value, "read"): + self._add_file_from_data(key, value) + else: + self.form.setlistdefault(key).append(value) + + if mimetype is not None: + self.mimetype = mimetype + + @classmethod + def from_environ( + cls, environ: "WSGIEnvironment", **kwargs: t.Any + ) -> "EnvironBuilder": + """Turn an environ dict back into a builder. Any extra kwargs + override the args extracted from the environ. + + .. versionchanged:: 2.0 + Path and query values are passed through the WSGI decoding + dance to avoid double encoding. + + .. versionadded:: 0.15 + """ + headers = Headers(EnvironHeaders(environ)) + out = { + "path": _wsgi_decoding_dance(environ["PATH_INFO"]), + "base_url": cls._make_base_url( + environ["wsgi.url_scheme"], + headers.pop("Host"), + _wsgi_decoding_dance(environ["SCRIPT_NAME"]), + ), + "query_string": _wsgi_decoding_dance(environ["QUERY_STRING"]), + "method": environ["REQUEST_METHOD"], + "input_stream": environ["wsgi.input"], + "content_type": headers.pop("Content-Type", None), + "content_length": headers.pop("Content-Length", None), + "errors_stream": environ["wsgi.errors"], + "multithread": environ["wsgi.multithread"], + "multiprocess": environ["wsgi.multiprocess"], + "run_once": environ["wsgi.run_once"], + "headers": headers, + } + out.update(kwargs) + return cls(**out) + + def _add_file_from_data( + self, + key: str, + value: t.Union[ + t.BinaryIO, t.Tuple[t.BinaryIO, str], t.Tuple[t.BinaryIO, str, str] + ], + ) -> None: + """Called in the EnvironBuilder to add files from the data dict.""" + if isinstance(value, tuple): + self.files.add_file(key, *value) + else: + self.files.add_file(key, value) + + @staticmethod + def _make_base_url(scheme: str, host: str, script_root: str) -> str: + return url_unparse((scheme, host, script_root, "", "")).rstrip("/") + "/" + + @property + def base_url(self) -> str: + """The base URL is used to extract the URL scheme, host name, + port, and root path. + """ + return self._make_base_url(self.url_scheme, self.host, self.script_root) + + @base_url.setter + def base_url(self, value: t.Optional[str]) -> None: + if value is None: + scheme = "http" + netloc = "localhost" + script_root = "" + else: + scheme, netloc, script_root, qs, anchor = url_parse(value) + if qs or anchor: + raise ValueError("base url must not contain a query string or fragment") + self.script_root = script_root.rstrip("/") + self.host = netloc + self.url_scheme = scheme + + @property + def content_type(self) -> t.Optional[str]: + """The content type for the request. Reflected from and to + the :attr:`headers`. Do not set if you set :attr:`files` or + :attr:`form` for auto detection. + """ + ct = self.headers.get("Content-Type") + if ct is None and not self._input_stream: + if self._files: + return "multipart/form-data" + if self._form: + return "application/x-www-form-urlencoded" + return None + return ct + + @content_type.setter + def content_type(self, value: t.Optional[str]) -> None: + if value is None: + self.headers.pop("Content-Type", None) + else: + self.headers["Content-Type"] = value + + @property + def mimetype(self) -> t.Optional[str]: + """The mimetype (content type without charset etc.) + + .. versionadded:: 0.14 + """ + ct = self.content_type + return ct.split(";")[0].strip() if ct else None + + @mimetype.setter + def mimetype(self, value: str) -> None: + self.content_type = get_content_type(value, self.charset) + + @property + def mimetype_params(self) -> t.Mapping[str, str]: + """The mimetype parameters as dict. For example if the + content type is ``text/html; charset=utf-8`` the params would be + ``{'charset': 'utf-8'}``. + + .. versionadded:: 0.14 + """ + + def on_update(d: t.Mapping[str, str]) -> None: + self.headers["Content-Type"] = dump_options_header(self.mimetype, d) + + d = parse_options_header(self.headers.get("content-type", ""))[1] + return CallbackDict(d, on_update) + + @property + def content_length(self) -> t.Optional[int]: + """The content length as integer. Reflected from and to the + :attr:`headers`. Do not set if you set :attr:`files` or + :attr:`form` for auto detection. + """ + return self.headers.get("Content-Length", type=int) + + @content_length.setter + def content_length(self, value: t.Optional[int]) -> None: + if value is None: + self.headers.pop("Content-Length", None) + else: + self.headers["Content-Length"] = str(value) + + def _get_form(self, name: str, storage: t.Type[_TAnyMultiDict]) -> _TAnyMultiDict: + """Common behavior for getting the :attr:`form` and + :attr:`files` properties. + + :param name: Name of the internal cached attribute. + :param storage: Storage class used for the data. + """ + if self.input_stream is not None: + raise AttributeError("an input stream is defined") + + rv = getattr(self, name) + + if rv is None: + rv = storage() + setattr(self, name, rv) + + return rv # type: ignore + + def _set_form(self, name: str, value: MultiDict) -> None: + """Common behavior for setting the :attr:`form` and + :attr:`files` properties. + + :param name: Name of the internal cached attribute. + :param value: Value to assign to the attribute. + """ + self._input_stream = None + setattr(self, name, value) + + @property + def form(self) -> MultiDict: + """A :class:`MultiDict` of form values.""" + return self._get_form("_form", MultiDict) + + @form.setter + def form(self, value: MultiDict) -> None: + self._set_form("_form", value) + + @property + def files(self) -> FileMultiDict: + """A :class:`FileMultiDict` of uploaded files. Use + :meth:`~FileMultiDict.add_file` to add new files. + """ + return self._get_form("_files", FileMultiDict) + + @files.setter + def files(self, value: FileMultiDict) -> None: + self._set_form("_files", value) + + @property + def input_stream(self) -> t.Optional[t.BinaryIO]: + """An optional input stream. This is mutually exclusive with + setting :attr:`form` and :attr:`files`, setting it will clear + those. Do not provide this if the method is not ``POST`` or + another method that has a body. + """ + return self._input_stream + + @input_stream.setter + def input_stream(self, value: t.Optional[t.BinaryIO]) -> None: + self._input_stream = value + self._form = None + self._files = None + + @property + def query_string(self) -> str: + """The query string. If you set this to a string + :attr:`args` will no longer be available. + """ + if self._query_string is None: + if self._args is not None: + return url_encode(self._args, charset=self.charset) + return "" + return self._query_string + + @query_string.setter + def query_string(self, value: t.Optional[str]) -> None: + self._query_string = value + self._args = None + + @property + def args(self) -> MultiDict: + """The URL arguments as :class:`MultiDict`.""" + if self._query_string is not None: + raise AttributeError("a query string is defined") + if self._args is None: + self._args = MultiDict() + return self._args + + @args.setter + def args(self, value: t.Optional[MultiDict]) -> None: + self._query_string = None + self._args = value + + @property + def server_name(self) -> str: + """The server name (read-only, use :attr:`host` to set)""" + return self.host.split(":", 1)[0] + + @property + def server_port(self) -> int: + """The server port as integer (read-only, use :attr:`host` to set)""" + pieces = self.host.split(":", 1) + if len(pieces) == 2 and pieces[1].isdigit(): + return int(pieces[1]) + if self.url_scheme == "https": + return 443 + return 80 + + def __del__(self) -> None: + try: + self.close() + except Exception: + pass + + def close(self) -> None: + """Closes all files. If you put real :class:`file` objects into the + :attr:`files` dict you can call this method to automatically close + them all in one go. + """ + if self.closed: + return + try: + files = self.files.values() + except AttributeError: + files = () # type: ignore + for f in files: + try: + f.close() + except Exception: + pass + self.closed = True + + def get_environ(self) -> "WSGIEnvironment": + """Return the built environ. + + .. versionchanged:: 0.15 + The content type and length headers are set based on + input stream detection. Previously this only set the WSGI + keys. + """ + input_stream = self.input_stream + content_length = self.content_length + + mimetype = self.mimetype + content_type = self.content_type + + if input_stream is not None: + start_pos = input_stream.tell() + input_stream.seek(0, 2) + end_pos = input_stream.tell() + input_stream.seek(start_pos) + content_length = end_pos - start_pos + elif mimetype == "multipart/form-data": + input_stream, content_length, boundary = stream_encode_multipart( + CombinedMultiDict([self.form, self.files]), charset=self.charset + ) + content_type = f'{mimetype}; boundary="{boundary}"' + elif mimetype == "application/x-www-form-urlencoded": + form_encoded = url_encode(self.form, charset=self.charset).encode("ascii") + content_length = len(form_encoded) + input_stream = BytesIO(form_encoded) + else: + input_stream = BytesIO() + + result: "WSGIEnvironment" = {} + if self.environ_base: + result.update(self.environ_base) + + def _path_encode(x: str) -> str: + return _wsgi_encoding_dance(url_unquote(x, self.charset), self.charset) + + raw_uri = _wsgi_encoding_dance(self.request_uri, self.charset) + result.update( + { + "REQUEST_METHOD": self.method, + "SCRIPT_NAME": _path_encode(self.script_root), + "PATH_INFO": _path_encode(self.path), + "QUERY_STRING": _wsgi_encoding_dance(self.query_string, self.charset), + # Non-standard, added by mod_wsgi, uWSGI + "REQUEST_URI": raw_uri, + # Non-standard, added by gunicorn + "RAW_URI": raw_uri, + "SERVER_NAME": self.server_name, + "SERVER_PORT": str(self.server_port), + "HTTP_HOST": self.host, + "SERVER_PROTOCOL": self.server_protocol, + "wsgi.version": self.wsgi_version, + "wsgi.url_scheme": self.url_scheme, + "wsgi.input": input_stream, + "wsgi.errors": self.errors_stream, + "wsgi.multithread": self.multithread, + "wsgi.multiprocess": self.multiprocess, + "wsgi.run_once": self.run_once, + } + ) + + headers = self.headers.copy() + + if content_type is not None: + result["CONTENT_TYPE"] = content_type + headers.set("Content-Type", content_type) + + if content_length is not None: + result["CONTENT_LENGTH"] = str(content_length) + headers.set("Content-Length", content_length) + + combined_headers = defaultdict(list) + + for key, value in headers.to_wsgi_list(): + combined_headers[f"HTTP_{key.upper().replace('-', '_')}"].append(value) + + for key, values in combined_headers.items(): + result[key] = ", ".join(values) + + if self.environ_overrides: + result.update(self.environ_overrides) + + return result + + def get_request(self, cls: t.Optional[t.Type[Request]] = None) -> Request: + """Returns a request with the data. If the request class is not + specified :attr:`request_class` is used. + + :param cls: The request wrapper to use. + """ + if cls is None: + cls = self.request_class + + return cls(self.get_environ()) + + +class ClientRedirectError(Exception): + """If a redirect loop is detected when using follow_redirects=True with + the :cls:`Client`, then this exception is raised. + """ + + +class Client: + """This class allows you to send requests to a wrapped application. + + The use_cookies parameter indicates whether cookies should be stored and + sent for subsequent requests. This is True by default, but passing False + will disable this behaviour. + + If you want to request some subdomain of your application you may set + `allow_subdomain_redirects` to `True` as if not no external redirects + are allowed. + + .. versionchanged:: 2.0 + ``response_wrapper`` is always a subclass of + :class:``TestResponse``. + + .. versionchanged:: 0.5 + Added the ``use_cookies`` parameter. + """ + + def __init__( + self, + application: "WSGIApplication", + response_wrapper: t.Optional[t.Type["Response"]] = None, + use_cookies: bool = True, + allow_subdomain_redirects: bool = False, + ) -> None: + self.application = application + + if response_wrapper in {None, Response}: + response_wrapper = TestResponse + elif not isinstance(response_wrapper, TestResponse): + response_wrapper = type( + "WrapperTestResponse", + (TestResponse, response_wrapper), # type: ignore + {}, + ) + + self.response_wrapper = t.cast(t.Type["TestResponse"], response_wrapper) + + if use_cookies: + self.cookie_jar: t.Optional[_TestCookieJar] = _TestCookieJar() + else: + self.cookie_jar = None + + self.allow_subdomain_redirects = allow_subdomain_redirects + + def set_cookie( + self, + server_name: str, + key: str, + value: str = "", + max_age: t.Optional[t.Union[timedelta, int]] = None, + expires: t.Optional[t.Union[str, datetime, int, float]] = None, + path: str = "/", + domain: t.Optional[str] = None, + secure: bool = False, + httponly: bool = False, + samesite: t.Optional[str] = None, + charset: str = "utf-8", + ) -> None: + """Sets a cookie in the client's cookie jar. The server name + is required and has to match the one that is also passed to + the open call. + """ + assert self.cookie_jar is not None, "cookies disabled" + header = dump_cookie( + key, + value, + max_age, + expires, + path, + domain, + secure, + httponly, + charset, + samesite=samesite, + ) + environ = create_environ(path, base_url=f"http://{server_name}") + headers = [("Set-Cookie", header)] + self.cookie_jar.extract_wsgi(environ, headers) + + def delete_cookie( + self, + server_name: str, + key: str, + path: str = "/", + domain: t.Optional[str] = None, + secure: bool = False, + httponly: bool = False, + samesite: t.Optional[str] = None, + ) -> None: + """Deletes a cookie in the test client.""" + self.set_cookie( + server_name, + key, + expires=0, + max_age=0, + path=path, + domain=domain, + secure=secure, + httponly=httponly, + samesite=samesite, + ) + + def run_wsgi_app( + self, environ: "WSGIEnvironment", buffered: bool = False + ) -> t.Tuple[t.Iterable[bytes], str, Headers]: + """Runs the wrapped WSGI app with the given environment. + + :meta private: + """ + if self.cookie_jar is not None: + self.cookie_jar.inject_wsgi(environ) + + rv = run_wsgi_app(self.application, environ, buffered=buffered) + + if self.cookie_jar is not None: + self.cookie_jar.extract_wsgi(environ, rv[2]) + + return rv + + def resolve_redirect( + self, response: "TestResponse", buffered: bool = False + ) -> "TestResponse": + """Perform a new request to the location given by the redirect + response to the previous request. + + :meta private: + """ + scheme, netloc, path, qs, anchor = url_parse(response.location) + builder = EnvironBuilder.from_environ(response.request.environ, query_string=qs) + + to_name_parts = netloc.split(":", 1)[0].split(".") + from_name_parts = builder.server_name.split(".") + + if to_name_parts != [""]: + # The new location has a host, use it for the base URL. + builder.url_scheme = scheme + builder.host = netloc + else: + # A local redirect with autocorrect_location_header=False + # doesn't have a host, so use the request's host. + to_name_parts = from_name_parts + + # Explain why a redirect to a different server name won't be followed. + if to_name_parts != from_name_parts: + if to_name_parts[-len(from_name_parts) :] == from_name_parts: + if not self.allow_subdomain_redirects: + raise RuntimeError("Following subdomain redirects is not enabled.") + else: + raise RuntimeError("Following external redirects is not supported.") + + path_parts = path.split("/") + root_parts = builder.script_root.split("/") + + if path_parts[: len(root_parts)] == root_parts: + # Strip the script root from the path. + builder.path = path[len(builder.script_root) :] + else: + # The new location is not under the script root, so use the + # whole path and clear the previous root. + builder.path = path + builder.script_root = "" + + # Only 307 and 308 preserve all of the original request. + if response.status_code not in {307, 308}: + # HEAD is preserved, everything else becomes GET. + if builder.method != "HEAD": + builder.method = "GET" + + # Clear the body and the headers that describe it. + + if builder.input_stream is not None: + builder.input_stream.close() + builder.input_stream = None + + builder.content_type = None + builder.content_length = None + builder.headers.pop("Transfer-Encoding", None) + + return self.open(builder, buffered=buffered) + + def open( + self, + *args: t.Any, + as_tuple: bool = False, + buffered: bool = False, + follow_redirects: bool = False, + **kwargs: t.Any, + ) -> "TestResponse": + """Generate an environ dict from the given arguments, make a + request to the application using it, and return the response. + + :param args: Passed to :class:`EnvironBuilder` to create the + environ for the request. If a single arg is passed, it can + be an existing :class:`EnvironBuilder` or an environ dict. + :param buffered: Convert the iterator returned by the app into + a list. If the iterator has a ``close()`` method, it is + called automatically. + :param follow_redirects: Make additional requests to follow HTTP + redirects until a non-redirect status is returned. + :attr:`TestResponse.history` lists the intermediate + responses. + + .. versionchanged:: 2.0 + ``as_tuple`` is deprecated and will be removed in Werkzeug + 2.1. Use :attr:`TestResponse.request` and + ``request.environ`` instead. + + .. versionchanged:: 2.0 + The request input stream is closed when calling + ``response.close()``. Input streams for redirects are + automatically closed. + + .. versionchanged:: 0.5 + If a dict is provided as file in the dict for the ``data`` + parameter the content type has to be called ``content_type`` + instead of ``mimetype``. This change was made for + consistency with :class:`werkzeug.FileWrapper`. + + .. versionchanged:: 0.5 + Added the ``follow_redirects`` parameter. + """ + request: t.Optional["Request"] = None + + if not kwargs and len(args) == 1: + arg = args[0] + + if isinstance(arg, EnvironBuilder): + request = arg.get_request() + elif isinstance(arg, dict): + request = EnvironBuilder.from_environ(arg).get_request() + elif isinstance(arg, Request): + request = arg + + if request is None: + builder = EnvironBuilder(*args, **kwargs) + + try: + request = builder.get_request() + finally: + builder.close() + + response = self.run_wsgi_app(request.environ, buffered=buffered) + response = self.response_wrapper(*response, request=request) + + redirects = set() + history: t.List["TestResponse"] = [] + + while follow_redirects and response.status_code in { + 301, + 302, + 303, + 305, + 307, + 308, + }: + # Exhaust intermediate response bodies to ensure middleware + # that returns an iterator runs any cleanup code. + if not buffered: + response.make_sequence() + response.close() + + new_redirect_entry = (response.location, response.status_code) + + if new_redirect_entry in redirects: + raise ClientRedirectError( + f"Loop detected: A {response.status_code} redirect" + f" to {response.location} was already made." + ) + + redirects.add(new_redirect_entry) + response.history = tuple(history) + history.append(response) + response = self.resolve_redirect(response, buffered=buffered) + else: + # This is the final request after redirects, or not + # following redirects. + response.history = tuple(history) + # Close the input stream when closing the response, in case + # the input is an open temporary file. + response.call_on_close(request.input_stream.close) + + if as_tuple: + warnings.warn( + "'as_tuple' is deprecated and will be removed in" + " Werkzeug 2.1. Access 'response.request.environ'" + " instead.", + DeprecationWarning, + stacklevel=2, + ) + return request.environ, response # type: ignore + + return response + + def get(self, *args: t.Any, **kw: t.Any) -> "TestResponse": + """Call :meth:`open` with ``method`` set to ``GET``.""" + kw["method"] = "GET" + return self.open(*args, **kw) + + def post(self, *args: t.Any, **kw: t.Any) -> "TestResponse": + """Call :meth:`open` with ``method`` set to ``POST``.""" + kw["method"] = "POST" + return self.open(*args, **kw) + + def put(self, *args: t.Any, **kw: t.Any) -> "TestResponse": + """Call :meth:`open` with ``method`` set to ``PUT``.""" + kw["method"] = "PUT" + return self.open(*args, **kw) + + def delete(self, *args: t.Any, **kw: t.Any) -> "TestResponse": + """Call :meth:`open` with ``method`` set to ``DELETE``.""" + kw["method"] = "DELETE" + return self.open(*args, **kw) + + def patch(self, *args: t.Any, **kw: t.Any) -> "TestResponse": + """Call :meth:`open` with ``method`` set to ``PATCH``.""" + kw["method"] = "PATCH" + return self.open(*args, **kw) + + def options(self, *args: t.Any, **kw: t.Any) -> "TestResponse": + """Call :meth:`open` with ``method`` set to ``OPTIONS``.""" + kw["method"] = "OPTIONS" + return self.open(*args, **kw) + + def head(self, *args: t.Any, **kw: t.Any) -> "TestResponse": + """Call :meth:`open` with ``method`` set to ``HEAD``.""" + kw["method"] = "HEAD" + return self.open(*args, **kw) + + def trace(self, *args: t.Any, **kw: t.Any) -> "TestResponse": + """Call :meth:`open` with ``method`` set to ``TRACE``.""" + kw["method"] = "TRACE" + return self.open(*args, **kw) + + def __repr__(self) -> str: + return f"<{type(self).__name__} {self.application!r}>" + + +def create_environ(*args: t.Any, **kwargs: t.Any) -> "WSGIEnvironment": + """Create a new WSGI environ dict based on the values passed. The first + parameter should be the path of the request which defaults to '/'. The + second one can either be an absolute path (in that case the host is + localhost:80) or a full path to the request with scheme, netloc port and + the path to the script. + + This accepts the same arguments as the :class:`EnvironBuilder` + constructor. + + .. versionchanged:: 0.5 + This function is now a thin wrapper over :class:`EnvironBuilder` which + was added in 0.5. The `headers`, `environ_base`, `environ_overrides` + and `charset` parameters were added. + """ + builder = EnvironBuilder(*args, **kwargs) + + try: + return builder.get_environ() + finally: + builder.close() + + +def run_wsgi_app( + app: "WSGIApplication", environ: "WSGIEnvironment", buffered: bool = False +) -> t.Tuple[t.Iterable[bytes], str, Headers]: + """Return a tuple in the form (app_iter, status, headers) of the + application output. This works best if you pass it an application that + returns an iterator all the time. + + Sometimes applications may use the `write()` callable returned + by the `start_response` function. This tries to resolve such edge + cases automatically. But if you don't get the expected output you + should set `buffered` to `True` which enforces buffering. + + If passed an invalid WSGI application the behavior of this function is + undefined. Never pass non-conforming WSGI applications to this function. + + :param app: the application to execute. + :param buffered: set to `True` to enforce buffering. + :return: tuple in the form ``(app_iter, status, headers)`` + """ + # Copy environ to ensure any mutations by the app (ProxyFix, for + # example) don't affect subsequent requests (such as redirects). + environ = _get_environ(environ).copy() + status: str + response: t.Optional[t.Tuple[str, t.List[t.Tuple[str, str]]]] = None + buffer: t.List[bytes] = [] + + def start_response(status, headers, exc_info=None): # type: ignore + nonlocal response + + if exc_info: + try: + raise exc_info[1].with_traceback(exc_info[2]) + finally: + exc_info = None + + response = (status, headers) + return buffer.append + + app_rv = app(environ, start_response) + close_func = getattr(app_rv, "close", None) + app_iter: t.Iterable[bytes] = iter(app_rv) + + # when buffering we emit the close call early and convert the + # application iterator into a regular list + if buffered: + try: + app_iter = list(app_iter) + finally: + if close_func is not None: + close_func() + + # otherwise we iterate the application iter until we have a response, chain + # the already received data with the already collected data and wrap it in + # a new `ClosingIterator` if we need to restore a `close` callable from the + # original return value. + else: + for item in app_iter: + buffer.append(item) + + if response is not None: + break + + if buffer: + app_iter = chain(buffer, app_iter) + + if close_func is not None and app_iter is not app_rv: + app_iter = ClosingIterator(app_iter, close_func) + + status, headers = response # type: ignore + return app_iter, status, Headers(headers) + + +class TestResponse(Response): + """:class:`~werkzeug.wrappers.Response` subclass that provides extra + information about requests made with the test :class:`Client`. + + Test client requests will always return an instance of this class. + If a custom response class is passed to the client, it is + subclassed along with this to support test information. + + If the test request included large files, or if the application is + serving a file, call :meth:`close` to close any open files and + prevent Python showing a ``ResourceWarning``. + """ + + request: Request + """A request object with the environ used to make the request that + resulted in this response. + """ + + history: t.Tuple["TestResponse", ...] + """A list of intermediate responses. Populated when the test request + is made with ``follow_redirects`` enabled. + """ + + def __init__( + self, + response: t.Iterable[bytes], + status: str, + headers: Headers, + request: Request, + history: t.Tuple["TestResponse"] = (), # type: ignore + **kwargs: t.Any, + ) -> None: + super().__init__(response, status, headers, **kwargs) + self.request = request + self.history = history + self._compat_tuple = response, status, headers + + def __iter__(self) -> t.Iterator: + warnings.warn( + ( + "The test client no longer returns a tuple, it returns" + " a 'TestResponse'. Tuple unpacking is deprecated and" + " will be removed in Werkzeug 2.1. Access the" + " attributes 'data', 'status', and 'headers' instead." + ), + DeprecationWarning, + stacklevel=2, + ) + return iter(self._compat_tuple) + + def __getitem__(self, item: int) -> t.Any: + warnings.warn( + ( + "The test client no longer returns a tuple, it returns" + " a 'TestResponse'. Item indexing is deprecated and" + " will be removed in Werkzeug 2.1. Access the" + " attributes 'data', 'status', and 'headers' instead." + ), + DeprecationWarning, + stacklevel=2, + ) + return self._compat_tuple[item] diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/testapp.py b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/testapp.py new file mode 100644 index 000000000..981f8878b --- /dev/null +++ b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/testapp.py @@ -0,0 +1,240 @@ +"""A small application that can be used to test a WSGI server and check +it for WSGI compliance. +""" +import base64 +import os +import sys +import typing as t +from html import escape +from textwrap import wrap + +from . import __version__ as _werkzeug_version +from .wrappers.request import Request +from .wrappers.response import Response + +if t.TYPE_CHECKING: + from _typeshed.wsgi import StartResponse + from _typeshed.wsgi import WSGIEnvironment + + +logo = Response( + base64.b64decode( + """ +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 +=""" + ), + mimetype="image/png", +) + + +TEMPLATE = """\ + +WSGI Information + +
    + +

    WSGI Information

    +

    + This page displays all available information about the WSGI server and + the underlying Python interpreter. +

    Python Interpreter

    + + + + + + +
    Python Version + %(python_version)s +
    Platform + %(platform)s [%(os)s] +
    API Version + %(api_version)s +
    Byteorder + %(byteorder)s +
    Werkzeug Version + %(werkzeug_version)s +
    +

    WSGI Environment

    + %(wsgi_env)s
    +

    Installed Eggs

    +

    + The following python packages were installed on the system as + Python eggs: +

      %(python_eggs)s
    +

    System Path

    +

    + The following paths are the current contents of the load path. The + following entries are looked up for Python packages. Note that not + all items in this path are folders. Gray and underlined items are + entries pointing to invalid resources or used by custom import hooks + such as the zip importer. +

    + Items with a bright background were expanded for display from a relative + path. If you encounter such paths in the output you might want to check + your setup as relative paths are usually problematic in multithreaded + environments. +

      %(sys_path)s
    +
    +""" + + +def iter_sys_path() -> t.Iterator[t.Tuple[str, bool, bool]]: + if os.name == "posix": + + def strip(x: str) -> str: + prefix = os.path.expanduser("~") + if x.startswith(prefix): + x = f"~{x[len(prefix) :]}" + return x + + else: + + def strip(x: str) -> str: + return x + + cwd = os.path.abspath(os.getcwd()) + for item in sys.path: + path = os.path.join(cwd, item or os.path.curdir) + yield strip(os.path.normpath(path)), not os.path.isdir(path), path != item + + +def render_testapp(req: Request) -> bytes: + try: + import pkg_resources + except ImportError: + eggs: t.Iterable[t.Any] = () + else: + eggs = sorted( + pkg_resources.working_set, + key=lambda x: x.project_name.lower(), # type: ignore + ) + python_eggs = [] + for egg in eggs: + try: + version = egg.version + except (ValueError, AttributeError): + version = "unknown" + python_eggs.append( + f"
  • {escape(egg.project_name)} [{escape(version)}]" + ) + + wsgi_env = [] + sorted_environ = sorted(req.environ.items(), key=lambda x: repr(x[0]).lower()) + for key, value in sorted_environ: + value = "".join(wrap(escape(repr(value)))) + wsgi_env.append(f"{escape(str(key))}{value}") + + sys_path = [] + for item, virtual, expanded in iter_sys_path(): + class_ = [] + if virtual: + class_.append("virtual") + if expanded: + class_.append("exp") + class_ = f' class="{" ".join(class_)}"' if class_ else "" + sys_path.append(f"{escape(item)}") + + return ( + TEMPLATE + % { + "python_version": "
    ".join(escape(sys.version).splitlines()), + "platform": escape(sys.platform), + "os": escape(os.name), + "api_version": sys.api_version, + "byteorder": sys.byteorder, + "werkzeug_version": _werkzeug_version, + "python_eggs": "\n".join(python_eggs), + "wsgi_env": "\n".join(wsgi_env), + "sys_path": "\n".join(sys_path), + } + ).encode("utf-8") + + +def test_app( + environ: "WSGIEnvironment", start_response: "StartResponse" +) -> t.Iterable[bytes]: + """Simple test application that dumps the environment. You can use + it to check if Werkzeug is working properly: + + .. sourcecode:: pycon + + >>> from werkzeug.serving import run_simple + >>> from werkzeug.testapp import test_app + >>> run_simple('localhost', 3000, test_app) + * Running on http://localhost:3000/ + + The application displays important information from the WSGI environment, + the Python interpreter and the installed libraries. + """ + req = Request(environ, populate_request=False) + if req.args.get("resource") == "logo": + response = logo + else: + response = Response(render_testapp(req), mimetype="text/html") + return response(environ, start_response) + + +if __name__ == "__main__": + from .serving import run_simple + + run_simple("localhost", 5000, test_app, use_reloader=True) diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/urls.py b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/urls.py new file mode 100644 index 000000000..7566ac273 --- /dev/null +++ b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/urls.py @@ -0,0 +1,1211 @@ +"""Functions for working with URLs. + +Contains implementations of functions from :mod:`urllib.parse` that +handle bytes and strings. +""" +import codecs +import os +import re +import typing as t +import warnings + +from ._internal import _check_str_tuple +from ._internal import _decode_idna +from ._internal import _encode_idna +from ._internal import _make_encode_wrapper +from ._internal import _to_str + +if t.TYPE_CHECKING: + from . import datastructures as ds + +# A regular expression for what a valid schema looks like +_scheme_re = re.compile(r"^[a-zA-Z0-9+-.]+$") + +# Characters that are safe in any part of an URL. +_always_safe = frozenset( + bytearray( + b"abcdefghijklmnopqrstuvwxyz" + b"ABCDEFGHIJKLMNOPQRSTUVWXYZ" + b"0123456789" + b"-._~" + ) +) + +_hexdigits = "0123456789ABCDEFabcdef" +_hextobyte = { + f"{a}{b}".encode("ascii"): int(f"{a}{b}", 16) + for a in _hexdigits + for b in _hexdigits +} +_bytetohex = [f"%{char:02X}".encode("ascii") for char in range(256)] + + +class _URLTuple(t.NamedTuple): + scheme: str + netloc: str + path: str + query: str + fragment: str + + +class BaseURL(_URLTuple): + """Superclass of :py:class:`URL` and :py:class:`BytesURL`.""" + + __slots__ = () + _at: str + _colon: str + _lbracket: str + _rbracket: str + + def __str__(self) -> str: + return self.to_url() + + def replace(self, **kwargs: t.Any) -> "BaseURL": + """Return an URL with the same values, except for those parameters + given new values by whichever keyword arguments are specified.""" + return self._replace(**kwargs) + + @property + def host(self) -> t.Optional[str]: + """The host part of the URL if available, otherwise `None`. The + host is either the hostname or the IP address mentioned in the + URL. It will not contain the port. + """ + return self._split_host()[0] + + @property + def ascii_host(self) -> t.Optional[str]: + """Works exactly like :attr:`host` but will return a result that + is restricted to ASCII. If it finds a netloc that is not ASCII + it will attempt to idna decode it. This is useful for socket + operations when the URL might include internationalized characters. + """ + rv = self.host + if rv is not None and isinstance(rv, str): + try: + rv = _encode_idna(rv) # type: ignore + except UnicodeError: + rv = rv.encode("ascii", "ignore") # type: ignore + return _to_str(rv, "ascii", "ignore") + + @property + def port(self) -> t.Optional[int]: + """The port in the URL as an integer if it was present, `None` + otherwise. This does not fill in default ports. + """ + try: + rv = int(_to_str(self._split_host()[1])) + if 0 <= rv <= 65535: + return rv + except (ValueError, TypeError): + pass + return None + + @property + def auth(self) -> t.Optional[str]: + """The authentication part in the URL if available, `None` + otherwise. + """ + return self._split_netloc()[0] + + @property + def username(self) -> t.Optional[str]: + """The username if it was part of the URL, `None` otherwise. + This undergoes URL decoding and will always be a string. + """ + rv = self._split_auth()[0] + if rv is not None: + return _url_unquote_legacy(rv) + return None + + @property + def raw_username(self) -> t.Optional[str]: + """The username if it was part of the URL, `None` otherwise. + Unlike :attr:`username` this one is not being decoded. + """ + return self._split_auth()[0] + + @property + def password(self) -> t.Optional[str]: + """The password if it was part of the URL, `None` otherwise. + This undergoes URL decoding and will always be a string. + """ + rv = self._split_auth()[1] + if rv is not None: + return _url_unquote_legacy(rv) + return None + + @property + def raw_password(self) -> t.Optional[str]: + """The password if it was part of the URL, `None` otherwise. + Unlike :attr:`password` this one is not being decoded. + """ + return self._split_auth()[1] + + def decode_query(self, *args: t.Any, **kwargs: t.Any) -> "ds.MultiDict[str, str]": + """Decodes the query part of the URL. Ths is a shortcut for + calling :func:`url_decode` on the query argument. The arguments and + keyword arguments are forwarded to :func:`url_decode` unchanged. + """ + return url_decode(self.query, *args, **kwargs) + + def join(self, *args: t.Any, **kwargs: t.Any) -> "BaseURL": + """Joins this URL with another one. This is just a convenience + function for calling into :meth:`url_join` and then parsing the + return value again. + """ + return url_parse(url_join(self, *args, **kwargs)) + + def to_url(self) -> str: + """Returns a URL string or bytes depending on the type of the + information stored. This is just a convenience function + for calling :meth:`url_unparse` for this URL. + """ + return url_unparse(self) + + def encode_netloc(self) -> str: + """Encodes the netloc part to an ASCII safe URL as bytes.""" + rv = self.ascii_host or "" + if ":" in rv: + rv = f"[{rv}]" + port = self.port + if port is not None: + rv = f"{rv}:{port}" + auth = ":".join( + filter( + None, + [ + url_quote(self.raw_username or "", "utf-8", "strict", "/:%"), + url_quote(self.raw_password or "", "utf-8", "strict", "/:%"), + ], + ) + ) + if auth: + rv = f"{auth}@{rv}" + return rv + + def decode_netloc(self) -> str: + """Decodes the netloc part into a string.""" + rv = _decode_idna(self.host or "") + + if ":" in rv: + rv = f"[{rv}]" + port = self.port + if port is not None: + rv = f"{rv}:{port}" + auth = ":".join( + filter( + None, + [ + _url_unquote_legacy(self.raw_username or "", "/:%@"), + _url_unquote_legacy(self.raw_password or "", "/:%@"), + ], + ) + ) + if auth: + rv = f"{auth}@{rv}" + return rv + + def to_uri_tuple(self) -> "BaseURL": + """Returns a :class:`BytesURL` tuple that holds a URI. This will + encode all the information in the URL properly to ASCII using the + rules a web browser would follow. + + It's usually more interesting to directly call :meth:`iri_to_uri` which + will return a string. + """ + return url_parse(iri_to_uri(self)) + + def to_iri_tuple(self) -> "BaseURL": + """Returns a :class:`URL` tuple that holds a IRI. This will try + to decode as much information as possible in the URL without + losing information similar to how a web browser does it for the + URL bar. + + It's usually more interesting to directly call :meth:`uri_to_iri` which + will return a string. + """ + return url_parse(uri_to_iri(self)) + + def get_file_location( + self, pathformat: t.Optional[str] = None + ) -> t.Tuple[t.Optional[str], t.Optional[str]]: + """Returns a tuple with the location of the file in the form + ``(server, location)``. If the netloc is empty in the URL or + points to localhost, it's represented as ``None``. + + The `pathformat` by default is autodetection but needs to be set + when working with URLs of a specific system. The supported values + are ``'windows'`` when working with Windows or DOS paths and + ``'posix'`` when working with posix paths. + + If the URL does not point to a local file, the server and location + are both represented as ``None``. + + :param pathformat: The expected format of the path component. + Currently ``'windows'`` and ``'posix'`` are + supported. Defaults to ``None`` which is + autodetect. + """ + if self.scheme != "file": + return None, None + + path = url_unquote(self.path) + host = self.netloc or None + + if pathformat is None: + if os.name == "nt": + pathformat = "windows" + else: + pathformat = "posix" + + if pathformat == "windows": + if path[:1] == "/" and path[1:2].isalpha() and path[2:3] in "|:": + path = f"{path[1:2]}:{path[3:]}" + windows_share = path[:3] in ("\\" * 3, "/" * 3) + import ntpath + + path = ntpath.normpath(path) + # Windows shared drives are represented as ``\\host\\directory``. + # That results in a URL like ``file://///host/directory``, and a + # path like ``///host/directory``. We need to special-case this + # because the path contains the hostname. + if windows_share and host is None: + parts = path.lstrip("\\").split("\\", 1) + if len(parts) == 2: + host, path = parts + else: + host = parts[0] + path = "" + elif pathformat == "posix": + import posixpath + + path = posixpath.normpath(path) + else: + raise TypeError(f"Invalid path format {pathformat!r}") + + if host in ("127.0.0.1", "::1", "localhost"): + host = None + + return host, path + + def _split_netloc(self) -> t.Tuple[t.Optional[str], str]: + if self._at in self.netloc: + auth, _, netloc = self.netloc.partition(self._at) + return auth, netloc + return None, self.netloc + + def _split_auth(self) -> t.Tuple[t.Optional[str], t.Optional[str]]: + auth = self._split_netloc()[0] + if not auth: + return None, None + if self._colon not in auth: + return auth, None + + username, _, password = auth.partition(self._colon) + return username, password + + def _split_host(self) -> t.Tuple[t.Optional[str], t.Optional[str]]: + rv = self._split_netloc()[1] + if not rv: + return None, None + + if not rv.startswith(self._lbracket): + if self._colon in rv: + host, _, port = rv.partition(self._colon) + return host, port + return rv, None + + idx = rv.find(self._rbracket) + if idx < 0: + return rv, None + + host = rv[1:idx] + rest = rv[idx + 1 :] + if rest.startswith(self._colon): + return host, rest[1:] + return host, None + + +class URL(BaseURL): + """Represents a parsed URL. This behaves like a regular tuple but + also has some extra attributes that give further insight into the + URL. + """ + + __slots__ = () + _at = "@" + _colon = ":" + _lbracket = "[" + _rbracket = "]" + + def encode(self, charset: str = "utf-8", errors: str = "replace") -> "BytesURL": + """Encodes the URL to a tuple made out of bytes. The charset is + only being used for the path, query and fragment. + """ + return BytesURL( + self.scheme.encode("ascii"), # type: ignore + self.encode_netloc(), + self.path.encode(charset, errors), # type: ignore + self.query.encode(charset, errors), # type: ignore + self.fragment.encode(charset, errors), # type: ignore + ) + + +class BytesURL(BaseURL): + """Represents a parsed URL in bytes.""" + + __slots__ = () + _at = b"@" # type: ignore + _colon = b":" # type: ignore + _lbracket = b"[" # type: ignore + _rbracket = b"]" # type: ignore + + def __str__(self) -> str: + return self.to_url().decode("utf-8", "replace") # type: ignore + + def encode_netloc(self) -> bytes: # type: ignore + """Returns the netloc unchanged as bytes.""" + return self.netloc # type: ignore + + def decode(self, charset: str = "utf-8", errors: str = "replace") -> "URL": + """Decodes the URL to a tuple made out of strings. The charset is + only being used for the path, query and fragment. + """ + return URL( + self.scheme.decode("ascii"), # type: ignore + self.decode_netloc(), + self.path.decode(charset, errors), # type: ignore + self.query.decode(charset, errors), # type: ignore + self.fragment.decode(charset, errors), # type: ignore + ) + + +_unquote_maps: t.Dict[t.FrozenSet[int], t.Dict[bytes, int]] = {frozenset(): _hextobyte} + + +def _unquote_to_bytes( + string: t.Union[str, bytes], unsafe: t.Union[str, bytes] = "" +) -> bytes: + if isinstance(string, str): + string = string.encode("utf-8") + + if isinstance(unsafe, str): + unsafe = unsafe.encode("utf-8") + + unsafe = frozenset(bytearray(unsafe)) + groups = iter(string.split(b"%")) + result = bytearray(next(groups, b"")) + + try: + hex_to_byte = _unquote_maps[unsafe] + except KeyError: + hex_to_byte = _unquote_maps[unsafe] = { + h: b for h, b in _hextobyte.items() if b not in unsafe + } + + for group in groups: + code = group[:2] + + if code in hex_to_byte: + result.append(hex_to_byte[code]) + result.extend(group[2:]) + else: + result.append(37) # % + result.extend(group) + + return bytes(result) + + +def _url_encode_impl( + obj: t.Union[t.Mapping[str, str], t.Iterable[t.Tuple[str, str]]], + charset: str, + sort: bool, + key: t.Optional[t.Callable[[t.Tuple[str, str]], t.Any]], +) -> t.Iterator[str]: + from .datastructures import iter_multi_items + + iterable: t.Iterable[t.Tuple[str, str]] = iter_multi_items(obj) + + if sort: + iterable = sorted(iterable, key=key) + + for key_str, value_str in iterable: + if value_str is None: + continue + + if not isinstance(key_str, bytes): + key_bytes = str(key_str).encode(charset) + else: + key_bytes = key_str + + if not isinstance(value_str, bytes): + value_bytes = str(value_str).encode(charset) + else: + value_bytes = value_str + + yield f"{_fast_url_quote_plus(key_bytes)}={_fast_url_quote_plus(value_bytes)}" + + +def _url_unquote_legacy(value: str, unsafe: str = "") -> str: + try: + return url_unquote(value, charset="utf-8", errors="strict", unsafe=unsafe) + except UnicodeError: + return url_unquote(value, charset="latin1", unsafe=unsafe) + + +def url_parse( + url: str, scheme: t.Optional[str] = None, allow_fragments: bool = True +) -> BaseURL: + """Parses a URL from a string into a :class:`URL` tuple. If the URL + is lacking a scheme it can be provided as second argument. Otherwise, + it is ignored. Optionally fragments can be stripped from the URL + by setting `allow_fragments` to `False`. + + The inverse of this function is :func:`url_unparse`. + + :param url: the URL to parse. + :param scheme: the default schema to use if the URL is schemaless. + :param allow_fragments: if set to `False` a fragment will be removed + from the URL. + """ + s = _make_encode_wrapper(url) + is_text_based = isinstance(url, str) + + if scheme is None: + scheme = s("") + netloc = query = fragment = s("") + i = url.find(s(":")) + if i > 0 and _scheme_re.match(_to_str(url[:i], errors="replace")): + # make sure "iri" is not actually a port number (in which case + # "scheme" is really part of the path) + rest = url[i + 1 :] + if not rest or any(c not in s("0123456789") for c in rest): + # not a port number + scheme, url = url[:i].lower(), rest + + if url[:2] == s("//"): + delim = len(url) + for c in s("/?#"): + wdelim = url.find(c, 2) + if wdelim >= 0: + delim = min(delim, wdelim) + netloc, url = url[2:delim], url[delim:] + if (s("[") in netloc and s("]") not in netloc) or ( + s("]") in netloc and s("[") not in netloc + ): + raise ValueError("Invalid IPv6 URL") + + if allow_fragments and s("#") in url: + url, fragment = url.split(s("#"), 1) + if s("?") in url: + url, query = url.split(s("?"), 1) + + result_type = URL if is_text_based else BytesURL + return result_type(scheme, netloc, url, query, fragment) + + +def _make_fast_url_quote( + charset: str = "utf-8", + errors: str = "strict", + safe: t.Union[str, bytes] = "/:", + unsafe: t.Union[str, bytes] = "", +) -> t.Callable[[bytes], str]: + """Precompile the translation table for a URL encoding function. + + Unlike :func:`url_quote`, the generated function only takes the + string to quote. + + :param charset: The charset to encode the result with. + :param errors: How to handle encoding errors. + :param safe: An optional sequence of safe characters to never encode. + :param unsafe: An optional sequence of unsafe characters to always encode. + """ + if isinstance(safe, str): + safe = safe.encode(charset, errors) + + if isinstance(unsafe, str): + unsafe = unsafe.encode(charset, errors) + + safe = (frozenset(bytearray(safe)) | _always_safe) - frozenset(bytearray(unsafe)) + table = [chr(c) if c in safe else f"%{c:02X}" for c in range(256)] + + def quote(string: bytes) -> str: + return "".join([table[c] for c in string]) + + return quote + + +_fast_url_quote = _make_fast_url_quote() +_fast_quote_plus = _make_fast_url_quote(safe=" ", unsafe="+") + + +def _fast_url_quote_plus(string: bytes) -> str: + return _fast_quote_plus(string).replace(" ", "+") + + +def url_quote( + string: t.Union[str, bytes], + charset: str = "utf-8", + errors: str = "strict", + safe: t.Union[str, bytes] = "/:", + unsafe: t.Union[str, bytes] = "", +) -> str: + """URL encode a single string with a given encoding. + + :param s: the string to quote. + :param charset: the charset to be used. + :param safe: an optional sequence of safe characters. + :param unsafe: an optional sequence of unsafe characters. + + .. versionadded:: 0.9.2 + The `unsafe` parameter was added. + """ + if not isinstance(string, (str, bytes, bytearray)): + string = str(string) + if isinstance(string, str): + string = string.encode(charset, errors) + if isinstance(safe, str): + safe = safe.encode(charset, errors) + if isinstance(unsafe, str): + unsafe = unsafe.encode(charset, errors) + safe = (frozenset(bytearray(safe)) | _always_safe) - frozenset(bytearray(unsafe)) + rv = bytearray() + for char in bytearray(string): + if char in safe: + rv.append(char) + else: + rv.extend(_bytetohex[char]) + return bytes(rv).decode(charset) + + +def url_quote_plus( + string: str, charset: str = "utf-8", errors: str = "strict", safe: str = "" +) -> str: + """URL encode a single string with the given encoding and convert + whitespace to "+". + + :param s: The string to quote. + :param charset: The charset to be used. + :param safe: An optional sequence of safe characters. + """ + return url_quote(string, charset, errors, safe + " ", "+").replace(" ", "+") + + +def url_unparse(components: t.Tuple[str, str, str, str, str]) -> str: + """The reverse operation to :meth:`url_parse`. This accepts arbitrary + as well as :class:`URL` tuples and returns a URL as a string. + + :param components: the parsed URL as tuple which should be converted + into a URL string. + """ + _check_str_tuple(components) + scheme, netloc, path, query, fragment = components + s = _make_encode_wrapper(scheme) + url = s("") + + # We generally treat file:///x and file:/x the same which is also + # what browsers seem to do. This also allows us to ignore a schema + # register for netloc utilization or having to differentiate between + # empty and missing netloc. + if netloc or (scheme and path.startswith(s("/"))): + if path and path[:1] != s("/"): + path = s("/") + path + url = s("//") + (netloc or s("")) + path + elif path: + url += path + if scheme: + url = scheme + s(":") + url + if query: + url = url + s("?") + query + if fragment: + url = url + s("#") + fragment + return url + + +def url_unquote( + s: t.Union[str, bytes], + charset: str = "utf-8", + errors: str = "replace", + unsafe: str = "", +) -> str: + """URL decode a single string with a given encoding. If the charset + is set to `None` no decoding is performed and raw bytes are + returned. + + :param s: the string to unquote. + :param charset: the charset of the query string. If set to `None` + no decoding will take place. + :param errors: the error handling for the charset decoding. + """ + rv = _unquote_to_bytes(s, unsafe) + if charset is None: + return rv + return rv.decode(charset, errors) + + +def url_unquote_plus( + s: t.Union[str, bytes], charset: str = "utf-8", errors: str = "replace" +) -> str: + """URL decode a single string with the given `charset` and decode "+" to + whitespace. + + Per default encoding errors are ignored. If you want a different behavior + you can set `errors` to ``'replace'`` or ``'strict'``. + + :param s: The string to unquote. + :param charset: the charset of the query string. If set to `None` + no decoding will take place. + :param errors: The error handling for the `charset` decoding. + """ + if isinstance(s, str): + s = s.replace("+", " ") + else: + s = s.replace(b"+", b" ") + return url_unquote(s, charset, errors) + + +def url_fix(s: str, charset: str = "utf-8") -> str: + r"""Sometimes you get an URL by a user that just isn't a real URL because + it contains unsafe characters like ' ' and so on. This function can fix + some of the problems in a similar way browsers handle data entered by the + user: + + >>> url_fix('http://de.wikipedia.org/wiki/Elf (Begriffskl\xe4rung)') + 'http://de.wikipedia.org/wiki/Elf%20(Begriffskl%C3%A4rung)' + + :param s: the string with the URL to fix. + :param charset: The target charset for the URL if the url was given + as a string. + """ + # First step is to switch to text processing and to convert + # backslashes (which are invalid in URLs anyways) to slashes. This is + # consistent with what Chrome does. + s = _to_str(s, charset, "replace").replace("\\", "/") + + # For the specific case that we look like a malformed windows URL + # we want to fix this up manually: + if s.startswith("file://") and s[7:8].isalpha() and s[8:10] in (":/", "|/"): + s = f"file:///{s[7:]}" + + url = url_parse(s) + path = url_quote(url.path, charset, safe="/%+$!*'(),") + qs = url_quote_plus(url.query, charset, safe=":&%=+$!*'(),") + anchor = url_quote_plus(url.fragment, charset, safe=":&%=+$!*'(),") + return url_unparse((url.scheme, url.encode_netloc(), path, qs, anchor)) + + +# not-unreserved characters remain quoted when unquoting to IRI +_to_iri_unsafe = "".join([chr(c) for c in range(128) if c not in _always_safe]) + + +def _codec_error_url_quote(e: UnicodeError) -> t.Tuple[str, int]: + """Used in :func:`uri_to_iri` after unquoting to re-quote any + invalid bytes. + """ + # the docs state that UnicodeError does have these attributes, + # but mypy isn't picking them up + out = _fast_url_quote(e.object[e.start : e.end]) # type: ignore + return out, e.end # type: ignore + + +codecs.register_error("werkzeug.url_quote", _codec_error_url_quote) + + +def uri_to_iri( + uri: t.Union[str, t.Tuple[str, str, str, str, str]], + charset: str = "utf-8", + errors: str = "werkzeug.url_quote", +) -> str: + """Convert a URI to an IRI. All valid UTF-8 characters are unquoted, + leaving all reserved and invalid characters quoted. If the URL has + a domain, it is decoded from Punycode. + + >>> uri_to_iri("http://xn--n3h.net/p%C3%A5th?q=%C3%A8ry%DF") + 'http://\\u2603.net/p\\xe5th?q=\\xe8ry%DF' + + :param uri: The URI to convert. + :param charset: The encoding to encode unquoted bytes with. + :param errors: Error handler to use during ``bytes.encode``. By + default, invalid bytes are left quoted. + + .. versionchanged:: 0.15 + All reserved and invalid characters remain quoted. Previously, + only some reserved characters were preserved, and invalid bytes + were replaced instead of left quoted. + + .. versionadded:: 0.6 + """ + if isinstance(uri, tuple): + uri = url_unparse(uri) + + uri = url_parse(_to_str(uri, charset)) + path = url_unquote(uri.path, charset, errors, _to_iri_unsafe) + query = url_unquote(uri.query, charset, errors, _to_iri_unsafe) + fragment = url_unquote(uri.fragment, charset, errors, _to_iri_unsafe) + return url_unparse((uri.scheme, uri.decode_netloc(), path, query, fragment)) + + +# reserved characters remain unquoted when quoting to URI +_to_uri_safe = ":/?#[]@!$&'()*+,;=%" + + +def iri_to_uri( + iri: t.Union[str, t.Tuple[str, str, str, str, str]], + charset: str = "utf-8", + errors: str = "strict", + safe_conversion: bool = False, +) -> str: + """Convert an IRI to a URI. All non-ASCII and unsafe characters are + quoted. If the URL has a domain, it is encoded to Punycode. + + >>> iri_to_uri('http://\\u2603.net/p\\xe5th?q=\\xe8ry%DF') + 'http://xn--n3h.net/p%C3%A5th?q=%C3%A8ry%DF' + + :param iri: The IRI to convert. + :param charset: The encoding of the IRI. + :param errors: Error handler to use during ``bytes.encode``. + :param safe_conversion: Return the URL unchanged if it only contains + ASCII characters and no whitespace. See the explanation below. + + There is a general problem with IRI conversion with some protocols + that are in violation of the URI specification. Consider the + following two IRIs:: + + magnet:?xt=uri:whatever + itms-services://?action=download-manifest + + After parsing, we don't know if the scheme requires the ``//``, + which is dropped if empty, but conveys different meanings in the + final URL if it's present or not. In this case, you can use + ``safe_conversion``, which will return the URL unchanged if it only + contains ASCII characters and no whitespace. This can result in a + URI with unquoted characters if it was not already quoted correctly, + but preserves the URL's semantics. Werkzeug uses this for the + ``Location`` header for redirects. + + .. versionchanged:: 0.15 + All reserved characters remain unquoted. Previously, only some + reserved characters were left unquoted. + + .. versionchanged:: 0.9.6 + The ``safe_conversion`` parameter was added. + + .. versionadded:: 0.6 + """ + if isinstance(iri, tuple): + iri = url_unparse(iri) + + if safe_conversion: + # If we're not sure if it's safe to convert the URL, and it only + # contains ASCII characters, return it unconverted. + try: + native_iri = _to_str(iri) + ascii_iri = native_iri.encode("ascii") + + # Only return if it doesn't have whitespace. (Why?) + if len(ascii_iri.split()) == 1: + return native_iri + except UnicodeError: + pass + + iri = url_parse(_to_str(iri, charset, errors)) + path = url_quote(iri.path, charset, errors, _to_uri_safe) + query = url_quote(iri.query, charset, errors, _to_uri_safe) + fragment = url_quote(iri.fragment, charset, errors, _to_uri_safe) + return url_unparse((iri.scheme, iri.encode_netloc(), path, query, fragment)) + + +def url_decode( + s: t.AnyStr, + charset: str = "utf-8", + decode_keys: None = None, + include_empty: bool = True, + errors: str = "replace", + separator: str = "&", + cls: t.Optional[t.Type["ds.MultiDict"]] = None, +) -> "ds.MultiDict[str, str]": + """Parse a query string and return it as a :class:`MultiDict`. + + :param s: The query string to parse. + :param charset: Decode bytes to string with this charset. If not + given, bytes are returned as-is. + :param include_empty: Include keys with empty values in the dict. + :param errors: Error handling behavior when decoding bytes. + :param separator: Separator character between pairs. + :param cls: Container to hold result instead of :class:`MultiDict`. + + .. versionchanged:: 2.0 + The ``decode_keys`` parameter is deprecated and will be removed + in Werkzeug 2.1. + + .. versionchanged:: 0.5 + In previous versions ";" and "&" could be used for url decoding. + Now only "&" is supported. If you want to use ";", a different + ``separator`` can be provided. + + .. versionchanged:: 0.5 + The ``cls`` parameter was added. + """ + if decode_keys is not None: + warnings.warn( + "'decode_keys' is deprecated and will be removed in Werkzeug 2.1.", + DeprecationWarning, + stacklevel=2, + ) + if cls is None: + from .datastructures import MultiDict # noqa: F811 + + cls = MultiDict + if isinstance(s, str) and not isinstance(separator, str): + separator = separator.decode(charset or "ascii") + elif isinstance(s, bytes) and not isinstance(separator, bytes): + separator = separator.encode(charset or "ascii") # type: ignore + return cls( + _url_decode_impl( + s.split(separator), charset, include_empty, errors # type: ignore + ) + ) + + +def url_decode_stream( + stream: t.BinaryIO, + charset: str = "utf-8", + decode_keys: None = None, + include_empty: bool = True, + errors: str = "replace", + separator: bytes = b"&", + cls: t.Optional[t.Type["ds.MultiDict"]] = None, + limit: t.Optional[int] = None, + return_iterator: bool = False, +) -> "ds.MultiDict[str, str]": + """Works like :func:`url_decode` but decodes a stream. The behavior + of stream and limit follows functions like + :func:`~werkzeug.wsgi.make_line_iter`. The generator of pairs is + directly fed to the `cls` so you can consume the data while it's + parsed. + + :param stream: a stream with the encoded querystring + :param charset: the charset of the query string. If set to `None` + no decoding will take place. + :param include_empty: Set to `False` if you don't want empty values to + appear in the dict. + :param errors: the decoding error behavior. + :param separator: the pair separator to be used, defaults to ``&`` + :param cls: an optional dict class to use. If this is not specified + or `None` the default :class:`MultiDict` is used. + :param limit: the content length of the URL data. Not necessary if + a limited stream is provided. + + .. versionchanged:: 2.0 + The ``decode_keys`` and ``return_iterator`` parameters are + deprecated and will be removed in Werkzeug 2.1. + + .. versionadded:: 0.8 + """ + from .wsgi import make_chunk_iter + + if decode_keys is not None: + warnings.warn( + "'decode_keys' is deprecated and will be removed in Werkzeug 2.1.", + DeprecationWarning, + stacklevel=2, + ) + + pair_iter = make_chunk_iter(stream, separator, limit) + decoder = _url_decode_impl(pair_iter, charset, include_empty, errors) + + if return_iterator: + warnings.warn( + "'return_iterator' is deprecated and will be removed in Werkzeug 2.1.", + DeprecationWarning, + stacklevel=2, + ) + return decoder # type: ignore + + if cls is None: + from .datastructures import MultiDict # noqa: F811 + + cls = MultiDict + + return cls(decoder) + + +def _url_decode_impl( + pair_iter: t.Iterable[t.AnyStr], charset: str, include_empty: bool, errors: str +) -> t.Iterator[t.Tuple[str, str]]: + for pair in pair_iter: + if not pair: + continue + s = _make_encode_wrapper(pair) + equal = s("=") + if equal in pair: + key, value = pair.split(equal, 1) + else: + if not include_empty: + continue + key = pair + value = s("") + yield ( + url_unquote_plus(key, charset, errors), + url_unquote_plus(value, charset, errors), + ) + + +def url_encode( + obj: t.Union[t.Mapping[str, str], t.Iterable[t.Tuple[str, str]]], + charset: str = "utf-8", + encode_keys: None = None, + sort: bool = False, + key: t.Optional[t.Callable[[t.Tuple[str, str]], t.Any]] = None, + separator: str = "&", +) -> str: + """URL encode a dict/`MultiDict`. If a value is `None` it will not appear + in the result string. Per default only values are encoded into the target + charset strings. + + :param obj: the object to encode into a query string. + :param charset: the charset of the query string. + :param sort: set to `True` if you want parameters to be sorted by `key`. + :param separator: the separator to be used for the pairs. + :param key: an optional function to be used for sorting. For more details + check out the :func:`sorted` documentation. + + .. versionchanged:: 2.0 + The ``encode_keys`` parameter is deprecated and will be removed + in Werkzeug 2.1. + + .. versionchanged:: 0.5 + Added the ``sort``, ``key``, and ``separator`` parameters. + """ + if encode_keys is not None: + warnings.warn( + "'encode_keys' is deprecated and will be removed in Werkzeug 2.1.", + DeprecationWarning, + stacklevel=2, + ) + separator = _to_str(separator, "ascii") + return separator.join(_url_encode_impl(obj, charset, sort, key)) + + +def url_encode_stream( + obj: t.Union[t.Mapping[str, str], t.Iterable[t.Tuple[str, str]]], + stream: t.Optional[t.TextIO] = None, + charset: str = "utf-8", + encode_keys: None = None, + sort: bool = False, + key: t.Optional[t.Callable[[t.Tuple[str, str]], t.Any]] = None, + separator: str = "&", +) -> None: + """Like :meth:`url_encode` but writes the results to a stream + object. If the stream is `None` a generator over all encoded + pairs is returned. + + :param obj: the object to encode into a query string. + :param stream: a stream to write the encoded object into or `None` if + an iterator over the encoded pairs should be returned. In + that case the separator argument is ignored. + :param charset: the charset of the query string. + :param sort: set to `True` if you want parameters to be sorted by `key`. + :param separator: the separator to be used for the pairs. + :param key: an optional function to be used for sorting. For more details + check out the :func:`sorted` documentation. + + .. versionchanged:: 2.0 + The ``encode_keys`` parameter is deprecated and will be removed + in Werkzeug 2.1. + + .. versionadded:: 0.8 + """ + if encode_keys is not None: + warnings.warn( + "'encode_keys' is deprecated and will be removed in Werkzeug 2.1.", + DeprecationWarning, + stacklevel=2, + ) + separator = _to_str(separator, "ascii") + gen = _url_encode_impl(obj, charset, sort, key) + if stream is None: + return gen # type: ignore + for idx, chunk in enumerate(gen): + if idx: + stream.write(separator) + stream.write(chunk) + return None + + +def url_join( + base: t.Union[str, t.Tuple[str, str, str, str, str]], + url: t.Union[str, t.Tuple[str, str, str, str, str]], + allow_fragments: bool = True, +) -> str: + """Join a base URL and a possibly relative URL to form an absolute + interpretation of the latter. + + :param base: the base URL for the join operation. + :param url: the URL to join. + :param allow_fragments: indicates whether fragments should be allowed. + """ + if isinstance(base, tuple): + base = url_unparse(base) + if isinstance(url, tuple): + url = url_unparse(url) + + _check_str_tuple((base, url)) + s = _make_encode_wrapper(base) + + if not base: + return url + if not url: + return base + + bscheme, bnetloc, bpath, bquery, bfragment = url_parse( + base, allow_fragments=allow_fragments + ) + scheme, netloc, path, query, fragment = url_parse(url, bscheme, allow_fragments) + if scheme != bscheme: + return url + if netloc: + return url_unparse((scheme, netloc, path, query, fragment)) + netloc = bnetloc + + if path[:1] == s("/"): + segments = path.split(s("/")) + elif not path: + segments = bpath.split(s("/")) + if not query: + query = bquery + else: + segments = bpath.split(s("/"))[:-1] + path.split(s("/")) + + # If the rightmost part is "./" we want to keep the slash but + # remove the dot. + if segments[-1] == s("."): + segments[-1] = s("") + + # Resolve ".." and "." + segments = [segment for segment in segments if segment != s(".")] + while True: + i = 1 + n = len(segments) - 1 + while i < n: + if segments[i] == s("..") and segments[i - 1] not in (s(""), s("..")): + del segments[i - 1 : i + 1] + break + i += 1 + else: + break + + # Remove trailing ".." if the URL is absolute + unwanted_marker = [s(""), s("..")] + while segments[:2] == unwanted_marker: + del segments[1] + + path = s("/").join(segments) + return url_unparse((scheme, netloc, path, query, fragment)) + + +class Href: + """Implements a callable that constructs URLs with the given base. The + function can be called with any number of positional and keyword + arguments which than are used to assemble the URL. Works with URLs + and posix paths. + + Positional arguments are appended as individual segments to + the path of the URL: + + >>> href = Href('/foo') + >>> href('bar', 23) + '/foo/bar/23' + >>> href('foo', bar=23) + '/foo/foo?bar=23' + + If any of the arguments (positional or keyword) evaluates to `None` it + will be skipped. If no keyword arguments are given the last argument + can be a :class:`dict` or :class:`MultiDict` (or any other dict subclass), + otherwise the keyword arguments are used for the query parameters, cutting + off the first trailing underscore of the parameter name: + + >>> href(is_=42) + '/foo?is=42' + >>> href({'foo': 'bar'}) + '/foo?foo=bar' + + Combining of both methods is not allowed: + + >>> href({'foo': 'bar'}, bar=42) + Traceback (most recent call last): + ... + TypeError: keyword arguments and query-dicts can't be combined + + Accessing attributes on the href object creates a new href object with + the attribute name as prefix: + + >>> bar_href = href.bar + >>> bar_href("blub") + '/foo/bar/blub' + + If `sort` is set to `True` the items are sorted by `key` or the default + sorting algorithm: + + >>> href = Href("/", sort=True) + >>> href(a=1, b=2, c=3) + '/?a=1&b=2&c=3' + + .. deprecated:: 2.0 + Will be removed in Werkzeug 2.1. Use :mod:`werkzeug.routing` + instead. + + .. versionadded:: 0.5 + `sort` and `key` were added. + """ + + def __init__( # type: ignore + self, base="./", charset="utf-8", sort=False, key=None + ): + warnings.warn( + "'Href' is deprecated and will be removed in Werkzeug 2.1." + " Use 'werkzeug.routing' instead.", + DeprecationWarning, + stacklevel=2, + ) + + if not base: + base = "./" + self.base = base + self.charset = charset + self.sort = sort + self.key = key + + def __getattr__(self, name): # type: ignore + if name[:2] == "__": + raise AttributeError(name) + base = self.base + if base[-1:] != "/": + base += "/" + return Href(url_join(base, name), self.charset, self.sort, self.key) + + def __call__(self, *path, **query): # type: ignore + if path and isinstance(path[-1], dict): + if query: + raise TypeError("keyword arguments and query-dicts can't be combined") + query, path = path[-1], path[:-1] + elif query: + query = {k[:-1] if k.endswith("_") else k: v for k, v in query.items()} + path = "/".join( + [ + _to_str(url_quote(x, self.charset), "ascii") + for x in path + if x is not None + ] + ).lstrip("/") + rv = self.base + if path: + if not rv.endswith("/"): + rv += "/" + rv = url_join(rv, f"./{path}") + if query: + rv += "?" + _to_str( + url_encode(query, self.charset, sort=self.sort, key=self.key), "ascii" + ) + return rv diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/user_agent.py b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/user_agent.py new file mode 100644 index 000000000..66ffcbe07 --- /dev/null +++ b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/user_agent.py @@ -0,0 +1,47 @@ +import typing as t + + +class UserAgent: + """Represents a parsed user agent header value. + + The default implementation does no parsing, only the :attr:`string` + attribute is set. A subclass may parse the string to set the + common attributes or expose other information. Set + :attr:`werkzeug.wrappers.Request.user_agent_class` to use a + subclass. + + :param string: The header value to parse. + + .. versionadded:: 2.0 + This replaces the previous ``useragents`` module, but does not + provide a built-in parser. + """ + + platform: t.Optional[str] = None + """The OS name, if it could be parsed from the string.""" + + browser: t.Optional[str] = None + """The browser name, if it could be parsed from the string.""" + + version: t.Optional[str] = None + """The browser version, if it could be parsed from the string.""" + + language: t.Optional[str] = None + """The browser language, if it could be parsed from the string.""" + + def __init__(self, string: str) -> None: + self.string: str = string + """The original header value.""" + + def __repr__(self) -> str: + return f"<{type(self).__name__} {self.browser}/{self.version}>" + + def __str__(self) -> str: + return self.string + + def __bool__(self) -> bool: + return bool(self.browser) + + def to_header(self) -> str: + """Convert to a header value.""" + return self.string diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/useragents.py b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/useragents.py new file mode 100644 index 000000000..4deed8f46 --- /dev/null +++ b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/useragents.py @@ -0,0 +1,215 @@ +import re +import typing as t +import warnings + +from .user_agent import UserAgent as _BaseUserAgent + +if t.TYPE_CHECKING: + from _typeshed.wsgi import WSGIEnvironment + + +class _UserAgentParser: + platform_rules: t.ClassVar[t.Iterable[t.Tuple[str, str]]] = ( + (" cros ", "chromeos"), + ("iphone|ios", "iphone"), + ("ipad", "ipad"), + (r"darwin\b|mac\b|os\s*x", "macos"), + ("win", "windows"), + (r"android", "android"), + ("netbsd", "netbsd"), + ("openbsd", "openbsd"), + ("freebsd", "freebsd"), + ("dragonfly", "dragonflybsd"), + ("(sun|i86)os", "solaris"), + (r"x11\b|lin(\b|ux)?", "linux"), + (r"nintendo\s+wii", "wii"), + ("irix", "irix"), + ("hp-?ux", "hpux"), + ("aix", "aix"), + ("sco|unix_sv", "sco"), + ("bsd", "bsd"), + ("amiga", "amiga"), + ("blackberry|playbook", "blackberry"), + ("symbian", "symbian"), + ) + browser_rules: t.ClassVar[t.Iterable[t.Tuple[str, str]]] = ( + ("googlebot", "google"), + ("msnbot", "msn"), + ("yahoo", "yahoo"), + ("ask jeeves", "ask"), + (r"aol|america\s+online\s+browser", "aol"), + (r"opera|opr", "opera"), + ("edge|edg", "edge"), + ("chrome|crios", "chrome"), + ("seamonkey", "seamonkey"), + ("firefox|firebird|phoenix|iceweasel", "firefox"), + ("galeon", "galeon"), + ("safari|version", "safari"), + ("webkit", "webkit"), + ("camino", "camino"), + ("konqueror", "konqueror"), + ("k-meleon", "kmeleon"), + ("netscape", "netscape"), + (r"msie|microsoft\s+internet\s+explorer|trident/.+? rv:", "msie"), + ("lynx", "lynx"), + ("links", "links"), + ("Baiduspider", "baidu"), + ("bingbot", "bing"), + ("mozilla", "mozilla"), + ) + + _browser_version_re = r"(?:{pattern})[/\sa-z(]*(\d+[.\da-z]+)?" + _language_re = re.compile( + r"(?:;\s*|\s+)(\b\w{2}\b(?:-\b\w{2}\b)?)\s*;|" + r"(?:\(|\[|;)\s*(\b\w{2}\b(?:-\b\w{2}\b)?)\s*(?:\]|\)|;)" + ) + + def __init__(self) -> None: + self.platforms = [(b, re.compile(a, re.I)) for a, b in self.platform_rules] + self.browsers = [ + (b, re.compile(self._browser_version_re.format(pattern=a), re.I)) + for a, b in self.browser_rules + ] + + def __call__( + self, user_agent: str + ) -> t.Tuple[t.Optional[str], t.Optional[str], t.Optional[str], t.Optional[str]]: + platform: t.Optional[str] + browser: t.Optional[str] + version: t.Optional[str] + language: t.Optional[str] + + for platform, regex in self.platforms: # noqa: B007 + match = regex.search(user_agent) + if match is not None: + break + else: + platform = None + + # Except for Trident, all browser key words come after the last ')' + last_closing_paren = 0 + if ( + not re.compile(r"trident/.+? rv:", re.I).search(user_agent) + and ")" in user_agent + and user_agent[-1] != ")" + ): + last_closing_paren = user_agent.rindex(")") + + for browser, regex in self.browsers: # noqa: B007 + match = regex.search(user_agent[last_closing_paren:]) + if match is not None: + version = match.group(1) + break + else: + browser = version = None + match = self._language_re.search(user_agent) + if match is not None: + language = match.group(1) or match.group(2) + else: + language = None + return platform, browser, version, language + + +# It wasn't public, but users might have imported it anyway, show a +# warning if a user created an instance. +class UserAgentParser(_UserAgentParser): + """A simple user agent parser. Used by the `UserAgent`. + + .. deprecated:: 2.0 + Will be removed in Werkzeug 2.1. Use a dedicated parser library + instead. + """ + + def __init__(self) -> None: + warnings.warn( + "'UserAgentParser' is deprecated and will be removed in" + " Werkzeug 2.1. Use a dedicated parser library instead.", + DeprecationWarning, + stacklevel=2, + ) + super().__init__() + + +class _deprecated_property(property): + def __init__(self, fget: t.Callable[["_UserAgent"], t.Any]) -> None: + super().__init__(fget) + self.message = ( + "The built-in user agent parser is deprecated and will be" + f" removed in Werkzeug 2.1. The {fget.__name__!r} property" + " will be 'None'. Subclass 'werkzeug.user_agent.UserAgent'" + " and set 'Request.user_agent_class' to use a different" + " parser." + ) + + def __get__(self, *args: t.Any, **kwargs: t.Any) -> t.Any: + warnings.warn(self.message, DeprecationWarning, stacklevel=3) + return super().__get__(*args, **kwargs) + + +# This is what Request.user_agent returns for now, only show warnings on +# attribute access, not creation. +class _UserAgent(_BaseUserAgent): + _parser = _UserAgentParser() + + def __init__(self, string: str) -> None: + super().__init__(string) + info = self._parser(string) + self._platform, self._browser, self._version, self._language = info + + @_deprecated_property + def platform(self) -> t.Optional[str]: # type: ignore + return self._platform + + @_deprecated_property + def browser(self) -> t.Optional[str]: # type: ignore + return self._browser + + @_deprecated_property + def version(self) -> t.Optional[str]: # type: ignore + return self._version + + @_deprecated_property + def language(self) -> t.Optional[str]: # type: ignore + return self._language + + +# This is what users might be importing, show warnings on create. +class UserAgent(_UserAgent): + """Represents a parsed user agent header value. + + This uses a basic parser to try to extract some information from the + header. + + :param environ_or_string: The header value to parse, or a WSGI + environ containing the header. + + .. deprecated:: 2.0 + Will be removed in Werkzeug 2.1. Subclass + :class:`werkzeug.user_agent.UserAgent` (note the new module + name) to use a dedicated parser instead. + + .. versionchanged:: 2.0 + Passing a WSGI environ is deprecated and will be removed in 2.1. + """ + + def __init__(self, environ_or_string: "t.Union[str, WSGIEnvironment]") -> None: + if isinstance(environ_or_string, dict): + warnings.warn( + "Passing an environ to 'UserAgent' is deprecated and" + " will be removed in Werkzeug 2.1. Pass the header" + " value string instead.", + DeprecationWarning, + stacklevel=2, + ) + string = environ_or_string.get("HTTP_USER_AGENT", "") + else: + string = environ_or_string + + warnings.warn( + "The 'werkzeug.useragents' module is deprecated and will be" + " removed in Werkzeug 2.1. The new base API is" + " 'werkzeug.user_agent.UserAgent'.", + DeprecationWarning, + stacklevel=2, + ) + super().__init__(string) diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/utils.py b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/utils.py new file mode 100644 index 000000000..7bb02bbc1 --- /dev/null +++ b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/utils.py @@ -0,0 +1,1091 @@ +import codecs +import io +import mimetypes +import os +import pkgutil +import re +import sys +import typing as t +import unicodedata +import warnings +from datetime import datetime +from html.entities import name2codepoint +from time import time +from zlib import adler32 + +from ._internal import _DictAccessorProperty +from ._internal import _missing +from ._internal import _parse_signature +from ._internal import _TAccessorValue +from .datastructures import Headers +from .exceptions import NotFound +from .exceptions import RequestedRangeNotSatisfiable +from .security import safe_join +from .urls import url_quote +from .wsgi import wrap_file + +if t.TYPE_CHECKING: + from _typeshed.wsgi import WSGIEnvironment + from .wrappers.request import Request + from .wrappers.response import Response + +_T = t.TypeVar("_T") + +_entity_re = re.compile(r"&([^;]+);") +_filename_ascii_strip_re = re.compile(r"[^A-Za-z0-9_.-]") +_windows_device_files = ( + "CON", + "AUX", + "COM1", + "COM2", + "COM3", + "COM4", + "LPT1", + "LPT2", + "LPT3", + "PRN", + "NUL", +) + + +class cached_property(property, t.Generic[_T]): + """A :func:`property` that is only evaluated once. Subsequent access + returns the cached value. Setting the property sets the cached + value. Deleting the property clears the cached value, accessing it + again will evaluate it again. + + .. code-block:: python + + class Example: + @cached_property + def value(self): + # calculate something important here + return 42 + + e = Example() + e.value # evaluates + e.value # uses cache + e.value = 16 # sets cache + del e.value # clears cache + + The class must have a ``__dict__`` for this to work. + + .. versionchanged:: 2.0 + ``del obj.name`` clears the cached value. + """ + + def __init__( + self, + fget: t.Callable[[t.Any], _T], + name: t.Optional[str] = None, + doc: t.Optional[str] = None, + ) -> None: + super().__init__(fget, doc=doc) + self.__name__ = name or fget.__name__ + self.__module__ = fget.__module__ + + def __set__(self, obj: object, value: _T) -> None: + obj.__dict__[self.__name__] = value + + def __get__(self, obj: object, type: type = None) -> _T: # type: ignore + if obj is None: + return self # type: ignore + + value: _T = obj.__dict__.get(self.__name__, _missing) + + if value is _missing: + value = self.fget(obj) # type: ignore + obj.__dict__[self.__name__] = value + + return value + + def __delete__(self, obj: object) -> None: + del obj.__dict__[self.__name__] + + +def invalidate_cached_property(obj: object, name: str) -> None: + """Invalidates the cache for a :class:`cached_property`: + + >>> class Test(object): + ... @cached_property + ... def magic_number(self): + ... print("recalculating...") + ... return 42 + ... + >>> var = Test() + >>> var.magic_number + recalculating... + 42 + >>> var.magic_number + 42 + >>> invalidate_cached_property(var, "magic_number") + >>> var.magic_number + recalculating... + 42 + + You must pass the name of the cached property as the second argument. + + .. deprecated:: 2.0 + Will be removed in Werkzeug 2.1. Use ``del obj.name`` instead. + """ + warnings.warn( + "'invalidate_cached_property' is deprecated and will be removed" + " in Werkzeug 2.1. Use 'del obj.name' instead.", + DeprecationWarning, + stacklevel=2, + ) + delattr(obj, name) + + +class environ_property(_DictAccessorProperty[_TAccessorValue]): + """Maps request attributes to environment variables. This works not only + for the Werkzeug request object, but also any other class with an + environ attribute: + + >>> class Test(object): + ... environ = {'key': 'value'} + ... test = environ_property('key') + >>> var = Test() + >>> var.test + 'value' + + If you pass it a second value it's used as default if the key does not + exist, the third one can be a converter that takes a value and converts + it. If it raises :exc:`ValueError` or :exc:`TypeError` the default value + is used. If no default value is provided `None` is used. + + Per default the property is read only. You have to explicitly enable it + by passing ``read_only=False`` to the constructor. + """ + + read_only = True + + def lookup(self, obj: "Request") -> "WSGIEnvironment": + return obj.environ + + +class header_property(_DictAccessorProperty[_TAccessorValue]): + """Like `environ_property` but for headers.""" + + def lookup(self, obj: t.Union["Request", "Response"]) -> Headers: + return obj.headers + + +class HTMLBuilder: + """Helper object for HTML generation. + + Per default there are two instances of that class. The `html` one, and + the `xhtml` one for those two dialects. The class uses keyword parameters + and positional parameters to generate small snippets of HTML. + + Keyword parameters are converted to XML/SGML attributes, positional + arguments are used as children. Because Python accepts positional + arguments before keyword arguments it's a good idea to use a list with the + star-syntax for some children: + + >>> html.p(class_='foo', *[html.a('foo', href='foo.html'), ' ', + ... html.a('bar', href='bar.html')]) + '

    foo bar

    ' + + This class works around some browser limitations and can not be used for + arbitrary SGML/XML generation. For that purpose lxml and similar + libraries exist. + + Calling the builder escapes the string passed: + + >>> html.p(html("")) + '

    <foo>

    ' + + .. deprecated:: 2.0 + Will be removed in Werkzeug 2.1. + """ + + _entity_re = re.compile(r"&([^;]+);") + _entities = name2codepoint.copy() + _entities["apos"] = 39 + _empty_elements = { + "area", + "base", + "basefont", + "br", + "col", + "command", + "embed", + "frame", + "hr", + "img", + "input", + "keygen", + "isindex", + "link", + "meta", + "param", + "source", + "wbr", + } + _boolean_attributes = { + "selected", + "checked", + "compact", + "declare", + "defer", + "disabled", + "ismap", + "multiple", + "nohref", + "noresize", + "noshade", + "nowrap", + } + _plaintext_elements = {"textarea"} + _c_like_cdata = {"script", "style"} + + def __init__(self, dialect): # type: ignore + self._dialect = dialect + + def __call__(self, s): # type: ignore + import html + + warnings.warn( + "'utils.HTMLBuilder' is deprecated and will be removed in Werkzeug 2.1.", + DeprecationWarning, + stacklevel=2, + ) + return html.escape(s) + + def __getattr__(self, tag): # type: ignore + import html + + warnings.warn( + "'utils.HTMLBuilder' is deprecated and will be removed in Werkzeug 2.1.", + DeprecationWarning, + stacklevel=2, + ) + if tag[:2] == "__": + raise AttributeError(tag) + + def proxy(*children, **arguments): # type: ignore + buffer = f"<{tag}" + for key, value in arguments.items(): + if value is None: + continue + if key[-1] == "_": + key = key[:-1] + if key in self._boolean_attributes: + if not value: + continue + if self._dialect == "xhtml": + value = f'="{key}"' + else: + value = "" + else: + value = f'="{html.escape(value)}"' + buffer += f" {key}{value}" + if not children and tag in self._empty_elements: + if self._dialect == "xhtml": + buffer += " />" + else: + buffer += ">" + return buffer + buffer += ">" + + children_as_string = "".join([str(x) for x in children if x is not None]) + + if children_as_string: + if tag in self._plaintext_elements: + children_as_string = html.escape(children_as_string) + elif tag in self._c_like_cdata and self._dialect == "xhtml": + children_as_string = f"/**/" + buffer += children_as_string + f"" + return buffer + + return proxy + + def __repr__(self) -> str: + return f"<{type(self).__name__} for {self._dialect!r}>" + + +html = HTMLBuilder("html") +xhtml = HTMLBuilder("xhtml") + +# https://cgit.freedesktop.org/xdg/shared-mime-info/tree/freedesktop.org.xml.in +# https://www.iana.org/assignments/media-types/media-types.xhtml +# Types listed in the XDG mime info that have a charset in the IANA registration. +_charset_mimetypes = { + "application/ecmascript", + "application/javascript", + "application/sql", + "application/xml", + "application/xml-dtd", + "application/xml-external-parsed-entity", +} + + +def get_content_type(mimetype: str, charset: str) -> str: + """Returns the full content type string with charset for a mimetype. + + If the mimetype represents text, the charset parameter will be + appended, otherwise the mimetype is returned unchanged. + + :param mimetype: The mimetype to be used as content type. + :param charset: The charset to be appended for text mimetypes. + :return: The content type. + + .. versionchanged:: 0.15 + Any type that ends with ``+xml`` gets a charset, not just those + that start with ``application/``. Known text types such as + ``application/javascript`` are also given charsets. + """ + if ( + mimetype.startswith("text/") + or mimetype in _charset_mimetypes + or mimetype.endswith("+xml") + ): + mimetype += f"; charset={charset}" + + return mimetype + + +def detect_utf_encoding(data: bytes) -> str: + """Detect which UTF encoding was used to encode the given bytes. + + The latest JSON standard (:rfc:`8259`) suggests that only UTF-8 is + accepted. Older documents allowed 8, 16, or 32. 16 and 32 can be big + or little endian. Some editors or libraries may prepend a BOM. + + :internal: + + :param data: Bytes in unknown UTF encoding. + :return: UTF encoding name + + .. deprecated:: 2.0 + Will be removed in Werkzeug 2.1. This is built in to + :func:`json.loads`. + + .. versionadded:: 0.15 + """ + warnings.warn( + "'detect_utf_encoding' is deprecated and will be removed in" + " Werkzeug 2.1. This is built in to 'json.loads'.", + DeprecationWarning, + stacklevel=2, + ) + head = data[:4] + + if head[:3] == codecs.BOM_UTF8: + return "utf-8-sig" + + if b"\x00" not in head: + return "utf-8" + + if head in (codecs.BOM_UTF32_BE, codecs.BOM_UTF32_LE): + return "utf-32" + + if head[:2] in (codecs.BOM_UTF16_BE, codecs.BOM_UTF16_LE): + return "utf-16" + + if len(head) == 4: + if head[:3] == b"\x00\x00\x00": + return "utf-32-be" + + if head[::2] == b"\x00\x00": + return "utf-16-be" + + if head[1:] == b"\x00\x00\x00": + return "utf-32-le" + + if head[1::2] == b"\x00\x00": + return "utf-16-le" + + if len(head) == 2: + return "utf-16-be" if head.startswith(b"\x00") else "utf-16-le" + + return "utf-8" + + +def format_string(string: str, context: t.Mapping[str, t.Any]) -> str: + """String-template format a string: + + >>> format_string('$foo and ${foo}s', dict(foo=42)) + '42 and 42s' + + This does not do any attribute lookup. + + :param string: the format string. + :param context: a dict with the variables to insert. + + .. deprecated:: 2.0 + Will be removed in Werkzeug 2.1. Use :class:`string.Template` + instead. + """ + from string import Template + + warnings.warn( + "'utils.format_string' is deprecated and will be removed in" + " Werkzeug 2.1. Use 'string.Template' instead.", + DeprecationWarning, + stacklevel=2, + ) + return Template(string).substitute(context) + + +def secure_filename(filename: str) -> str: + r"""Pass it a filename and it will return a secure version of it. This + filename can then safely be stored on a regular file system and passed + to :func:`os.path.join`. The filename returned is an ASCII only string + for maximum portability. + + On windows systems the function also makes sure that the file is not + named after one of the special device files. + + >>> secure_filename("My cool movie.mov") + 'My_cool_movie.mov' + >>> secure_filename("../../../etc/passwd") + 'etc_passwd' + >>> secure_filename('i contain cool \xfcml\xe4uts.txt') + 'i_contain_cool_umlauts.txt' + + The function might return an empty filename. It's your responsibility + to ensure that the filename is unique and that you abort or + generate a random filename if the function returned an empty one. + + .. versionadded:: 0.5 + + :param filename: the filename to secure + """ + filename = unicodedata.normalize("NFKD", filename) + filename = filename.encode("ascii", "ignore").decode("ascii") + + for sep in os.path.sep, os.path.altsep: + if sep: + filename = filename.replace(sep, " ") + filename = str(_filename_ascii_strip_re.sub("", "_".join(filename.split()))).strip( + "._" + ) + + # on nt a couple of special files are present in each folder. We + # have to ensure that the target file is not such a filename. In + # this case we prepend an underline + if ( + os.name == "nt" + and filename + and filename.split(".")[0].upper() in _windows_device_files + ): + filename = f"_{filename}" + + return filename + + +def escape(s: t.Any) -> str: + """Replace ``&``, ``<``, ``>``, ``"``, and ``'`` with HTML-safe + sequences. + + ``None`` is escaped to an empty string. + + .. deprecated:: 2.0 + Will be removed in Werkzeug 2.1. Use MarkupSafe instead. + """ + import html + + warnings.warn( + "'utils.escape' is deprecated and will be removed in Werkzeug" + " 2.1. Use MarkupSafe instead.", + DeprecationWarning, + stacklevel=2, + ) + + if s is None: + return "" + + if hasattr(s, "__html__"): + return s.__html__() # type: ignore + + if not isinstance(s, str): + s = str(s) + + return html.escape(s, quote=True) # type: ignore + + +def unescape(s: str) -> str: + """The reverse of :func:`escape`. This unescapes all the HTML + entities, not only those inserted by ``escape``. + + .. deprecated:: 2.0 + Will be removed in Werkzeug 2.1. Use MarkupSafe instead. + """ + import html + + warnings.warn( + "'utils.unescape' is deprecated and will be removed in Werkzueg" + " 2.1. Use MarkupSafe instead.", + DeprecationWarning, + stacklevel=2, + ) + return html.unescape(s) + + +def redirect( + location: str, code: int = 302, Response: t.Optional[t.Type["Response"]] = None +) -> "Response": + """Returns a response object (a WSGI application) that, if called, + redirects the client to the target location. Supported codes are + 301, 302, 303, 305, 307, and 308. 300 is not supported because + it's not a real redirect and 304 because it's the answer for a + request with a request with defined If-Modified-Since headers. + + .. versionadded:: 0.6 + The location can now be a unicode string that is encoded using + the :func:`iri_to_uri` function. + + .. versionadded:: 0.10 + The class used for the Response object can now be passed in. + + :param location: the location the response should redirect to. + :param code: the redirect status code. defaults to 302. + :param class Response: a Response class to use when instantiating a + response. The default is :class:`werkzeug.wrappers.Response` if + unspecified. + """ + import html + + if Response is None: + from .wrappers import Response # type: ignore + + display_location = html.escape(location) + if isinstance(location, str): + # Safe conversion is necessary here as we might redirect + # to a broken URI scheme (for instance itms-services). + from .urls import iri_to_uri + + location = iri_to_uri(location, safe_conversion=True) + response = Response( # type: ignore + '\n' + "Redirecting...\n" + "

    Redirecting...

    \n" + "

    You should be redirected automatically to target URL: " + f'{display_location}. If' + " not click the link.", + code, + mimetype="text/html", + ) + response.headers["Location"] = location + return response + + +def append_slash_redirect(environ: "WSGIEnvironment", code: int = 301) -> "Response": + """Redirects to the same URL but with a slash appended. The behavior + of this function is undefined if the path ends with a slash already. + + :param environ: the WSGI environment for the request that triggers + the redirect. + :param code: the status code for the redirect. + """ + new_path = environ["PATH_INFO"].strip("/") + "/" + query_string = environ.get("QUERY_STRING") + if query_string: + new_path += f"?{query_string}" + return redirect(new_path, code) + + +def send_file( + path_or_file: t.Union[os.PathLike, str, t.BinaryIO], + environ: "WSGIEnvironment", + mimetype: t.Optional[str] = None, + as_attachment: bool = False, + download_name: t.Optional[str] = None, + conditional: bool = True, + etag: t.Union[bool, str] = True, + last_modified: t.Optional[t.Union[datetime, int, float]] = None, + max_age: t.Optional[ + t.Union[int, t.Callable[[t.Optional[str]], t.Optional[int]]] + ] = None, + use_x_sendfile: bool = False, + response_class: t.Optional[t.Type["Response"]] = None, + _root_path: t.Optional[t.Union[os.PathLike, str]] = None, +) -> "Response": + """Send the contents of a file to the client. + + The first argument can be a file path or a file-like object. Paths + are preferred in most cases because Werkzeug can manage the file and + get extra information from the path. Passing a file-like object + requires that the file is opened in binary mode, and is mostly + useful when building a file in memory with :class:`io.BytesIO`. + + Never pass file paths provided by a user. The path is assumed to be + trusted, so a user could craft a path to access a file you didn't + intend. + + If the WSGI server sets a ``file_wrapper`` in ``environ``, it is + used, otherwise Werkzeug's built-in wrapper is used. Alternatively, + if the HTTP server supports ``X-Sendfile``, ``use_x_sendfile=True`` + will tell the server to send the given path, which is much more + efficient than reading it in Python. + + :param path_or_file: The path to the file to send, relative to the + current working directory if a relative path is given. + Alternatively, a file-like object opened in binary mode. Make + sure the file pointer is seeked to the start of the data. + :param environ: The WSGI environ for the current request. + :param mimetype: The MIME type to send for the file. If not + provided, it will try to detect it from the file name. + :param as_attachment: Indicate to a browser that it should offer to + save the file instead of displaying it. + :param download_name: The default name browsers will use when saving + the file. Defaults to the passed file name. + :param conditional: Enable conditional and range responses based on + request headers. Requires passing a file path and ``environ``. + :param etag: Calculate an ETag for the file, which requires passing + a file path. Can also be a string to use instead. + :param last_modified: The last modified time to send for the file, + in seconds. If not provided, it will try to detect it from the + file path. + :param max_age: How long the client should cache the file, in + seconds. If set, ``Cache-Control`` will be ``public``, otherwise + it will be ``no-cache`` to prefer conditional caching. + :param use_x_sendfile: Set the ``X-Sendfile`` header to let the + server to efficiently send the file. Requires support from the + HTTP server. Requires passing a file path. + :param response_class: Build the response using this class. Defaults + to :class:`~werkzeug.wrappers.Response`. + :param _root_path: Do not use. For internal use only. Use + :func:`send_from_directory` to safely send files under a path. + + .. versionadded:: 2.0 + Adapted from Flask's implementation. + + .. versionchanged:: 2.0 + ``download_name`` replaces Flask's ``attachment_filename`` + parameter. If ``as_attachment=False``, it is passed with + ``Content-Disposition: inline`` instead. + + .. versionchanged:: 2.0 + ``max_age`` replaces Flask's ``cache_timeout`` parameter. + ``conditional`` is enabled and ``max_age`` is not set by + default. + + .. versionchanged:: 2.0 + ``etag`` replaces Flask's ``add_etags`` parameter. It can be a + string to use instead of generating one. + + .. versionchanged:: 2.0 + If an encoding is returned when guessing ``mimetype`` from + ``download_name``, set the ``Content-Encoding`` header. + """ + if response_class is None: + from .wrappers import Response + + response_class = Response + + path: t.Optional[str] = None + file: t.Optional[t.BinaryIO] = None + size: t.Optional[int] = None + mtime: t.Optional[float] = None + headers = Headers() + + if isinstance(path_or_file, (os.PathLike, str)) or hasattr( + path_or_file, "__fspath__" + ): + path_or_file = t.cast(t.Union[os.PathLike, str], path_or_file) + + # Flask will pass app.root_path, allowing its send_file wrapper + # to not have to deal with paths. + if _root_path is not None: + path = os.path.join(_root_path, path_or_file) + else: + path = os.path.abspath(path_or_file) + + stat = os.stat(path) + size = stat.st_size + mtime = stat.st_mtime + else: + file = path_or_file + + if download_name is None and path is not None: + download_name = os.path.basename(path) + + if mimetype is None: + if download_name is None: + raise TypeError( + "Unable to detect the MIME type because a file name is" + " not available. Either set 'download_name', pass a" + " path instead of a file, or set 'mimetype'." + ) + + mimetype, encoding = mimetypes.guess_type(download_name) + + if mimetype is None: + mimetype = "application/octet-stream" + + if encoding is not None: + headers.set("Content-Encoding", encoding) + + if download_name is not None: + try: + download_name.encode("ascii") + except UnicodeEncodeError: + simple = unicodedata.normalize("NFKD", download_name) + simple = simple.encode("ascii", "ignore").decode("ascii") + quoted = url_quote(download_name, safe="") + names = {"filename": simple, "filename*": f"UTF-8''{quoted}"} + else: + names = {"filename": download_name} + + value = "attachment" if as_attachment else "inline" + headers.set("Content-Disposition", value, **names) + elif as_attachment: + raise TypeError( + "No name provided for attachment. Either set" + " 'download_name' or pass a path instead of a file." + ) + + if use_x_sendfile and path is not None: + headers["X-Sendfile"] = path + data = None + else: + if file is None: + file = open(path, "rb") # type: ignore + elif isinstance(file, io.BytesIO): + size = file.getbuffer().nbytes + elif isinstance(file, io.TextIOBase): + raise ValueError("Files must be opened in binary mode or use BytesIO.") + + data = wrap_file(environ, file) + + rv = response_class( + data, mimetype=mimetype, headers=headers, direct_passthrough=True + ) + + if size is not None: + rv.content_length = size + + if last_modified is not None: + rv.last_modified = last_modified # type: ignore + elif mtime is not None: + rv.last_modified = mtime # type: ignore + + rv.cache_control.no_cache = True + + # Flask will pass app.get_send_file_max_age, allowing its send_file + # wrapper to not have to deal with paths. + if callable(max_age): + max_age = max_age(path) + + if max_age is not None: + if max_age > 0: + rv.cache_control.no_cache = None + rv.cache_control.public = True + + rv.cache_control.max_age = max_age + rv.expires = int(time() + max_age) # type: ignore + + if isinstance(etag, str): + rv.set_etag(etag) + elif etag and path is not None: + check = adler32(path.encode("utf-8")) & 0xFFFFFFFF + rv.set_etag(f"{mtime}-{size}-{check}") + + if conditional: + try: + rv = rv.make_conditional(environ, accept_ranges=True, complete_length=size) + except RequestedRangeNotSatisfiable: + if file is not None: + file.close() + + raise + + # Some x-sendfile implementations incorrectly ignore the 304 + # status code and send the file anyway. + if rv.status_code == 304: + rv.headers.pop("x-sendfile", None) + + return rv + + +def send_from_directory( + directory: t.Union[os.PathLike, str], + path: t.Union[os.PathLike, str], + environ: "WSGIEnvironment", + **kwargs: t.Any, +) -> "Response": + """Send a file from within a directory using :func:`send_file`. + + This is a secure way to serve files from a folder, such as static + files or uploads. Uses :func:`~werkzeug.security.safe_join` to + ensure the path coming from the client is not maliciously crafted to + point outside the specified directory. + + If the final path does not point to an existing regular file, + returns a 404 :exc:`~werkzeug.exceptions.NotFound` error. + + :param directory: The directory that ``path`` must be located under. + :param path: The path to the file to send, relative to + ``directory``. + :param environ: The WSGI environ for the current request. + :param kwargs: Arguments to pass to :func:`send_file`. + + .. versionadded:: 2.0 + Adapted from Flask's implementation. + """ + path = safe_join(os.fspath(directory), os.fspath(path)) + + if path is None: + raise NotFound() + + # Flask will pass app.root_path, allowing its send_from_directory + # wrapper to not have to deal with paths. + if "_root_path" in kwargs: + path = os.path.join(kwargs["_root_path"], path) + + try: + if not os.path.isfile(path): + raise NotFound() + except ValueError: + # path contains null byte on Python < 3.8 + raise NotFound() + + return send_file(path, environ, **kwargs) + + +def import_string(import_name: str, silent: bool = False) -> t.Any: + """Imports an object based on a string. This is useful if you want to + use import paths as endpoints or something similar. An import path can + be specified either in dotted notation (``xml.sax.saxutils.escape``) + or with a colon as object delimiter (``xml.sax.saxutils:escape``). + + If `silent` is True the return value will be `None` if the import fails. + + :param import_name: the dotted name for the object to import. + :param silent: if set to `True` import errors are ignored and + `None` is returned instead. + :return: imported object + """ + import_name = import_name.replace(":", ".") + try: + try: + __import__(import_name) + except ImportError: + if "." not in import_name: + raise + else: + return sys.modules[import_name] + + module_name, obj_name = import_name.rsplit(".", 1) + module = __import__(module_name, globals(), locals(), [obj_name]) + try: + return getattr(module, obj_name) + except AttributeError as e: + raise ImportError(e) + + except ImportError as e: + if not silent: + raise ImportStringError(import_name, e).with_traceback(sys.exc_info()[2]) + + return None + + +def find_modules( + import_path: str, include_packages: bool = False, recursive: bool = False +) -> t.Iterator[str]: + """Finds all the modules below a package. This can be useful to + automatically import all views / controllers so that their metaclasses / + function decorators have a chance to register themselves on the + application. + + Packages are not returned unless `include_packages` is `True`. This can + also recursively list modules but in that case it will import all the + packages to get the correct load path of that module. + + :param import_path: the dotted name for the package to find child modules. + :param include_packages: set to `True` if packages should be returned, too. + :param recursive: set to `True` if recursion should happen. + :return: generator + """ + module = import_string(import_path) + path = getattr(module, "__path__", None) + if path is None: + raise ValueError(f"{import_path!r} is not a package") + basename = f"{module.__name__}." + for _importer, modname, ispkg in pkgutil.iter_modules(path): + modname = basename + modname + if ispkg: + if include_packages: + yield modname + if recursive: + yield from find_modules(modname, include_packages, True) + else: + yield modname + + +def validate_arguments(func, args, kwargs, drop_extra=True): # type: ignore + """Checks if the function accepts the arguments and keyword arguments. + Returns a new ``(args, kwargs)`` tuple that can safely be passed to + the function without causing a `TypeError` because the function signature + is incompatible. If `drop_extra` is set to `True` (which is the default) + any extra positional or keyword arguments are dropped automatically. + + The exception raised provides three attributes: + + `missing` + A set of argument names that the function expected but where + missing. + + `extra` + A dict of keyword arguments that the function can not handle but + where provided. + + `extra_positional` + A list of values that where given by positional argument but the + function cannot accept. + + This can be useful for decorators that forward user submitted data to + a view function:: + + from werkzeug.utils import ArgumentValidationError, validate_arguments + + def sanitize(f): + def proxy(request): + data = request.values.to_dict() + try: + args, kwargs = validate_arguments(f, (request,), data) + except ArgumentValidationError: + raise BadRequest('The browser failed to transmit all ' + 'the data expected.') + return f(*args, **kwargs) + return proxy + + :param func: the function the validation is performed against. + :param args: a tuple of positional arguments. + :param kwargs: a dict of keyword arguments. + :param drop_extra: set to `False` if you don't want extra arguments + to be silently dropped. + :return: tuple in the form ``(args, kwargs)``. + + .. deprecated:: 2.0 + Will be removed in Werkzeug 2.1. Use :func:`inspect.signature` + instead. + """ + warnings.warn( + "'utils.validate_arguments' is deprecated and will be removed" + " in Werkzeug 2.1. Use 'inspect.signature' instead.", + DeprecationWarning, + stacklevel=2, + ) + parser = _parse_signature(func) + args, kwargs, missing, extra, extra_positional = parser(args, kwargs)[:5] + if missing: + raise ArgumentValidationError(tuple(missing)) + elif (extra or extra_positional) and not drop_extra: + raise ArgumentValidationError(None, extra, extra_positional) + return tuple(args), kwargs + + +def bind_arguments(func, args, kwargs): # type: ignore + """Bind the arguments provided into a dict. When passed a function, + a tuple of arguments and a dict of keyword arguments `bind_arguments` + returns a dict of names as the function would see it. This can be useful + to implement a cache decorator that uses the function arguments to build + the cache key based on the values of the arguments. + + :param func: the function the arguments should be bound for. + :param args: tuple of positional arguments. + :param kwargs: a dict of keyword arguments. + :return: a :class:`dict` of bound keyword arguments. + + .. deprecated:: 2.0 + Will be removed in Werkzeug 2.1. Use :meth:`Signature.bind` + instead. + """ + warnings.warn( + "'utils.bind_arguments' is deprecated and will be removed in" + " Werkzeug 2.1. Use 'Signature.bind' instead.", + DeprecationWarning, + stacklevel=2, + ) + ( + args, + kwargs, + missing, + extra, + extra_positional, + arg_spec, + vararg_var, + kwarg_var, + ) = _parse_signature(func)(args, kwargs) + values = {} + for (name, _has_default, _default), value in zip(arg_spec, args): + values[name] = value + if vararg_var is not None: + values[vararg_var] = tuple(extra_positional) + elif extra_positional: + raise TypeError("too many positional arguments") + if kwarg_var is not None: + multikw = set(extra) & {x[0] for x in arg_spec} + if multikw: + raise TypeError( + f"got multiple values for keyword argument {next(iter(multikw))!r}" + ) + values[kwarg_var] = extra + elif extra: + raise TypeError(f"got unexpected keyword argument {next(iter(extra))!r}") + return values + + +class ArgumentValidationError(ValueError): + """Raised if :func:`validate_arguments` fails to validate + + .. deprecated:: 2.0 + Will be removed in Werkzeug 2.1 along with ``utils.bind`` and + ``validate_arguments``. + """ + + def __init__(self, missing=None, extra=None, extra_positional=None): # type: ignore + self.missing = set(missing or ()) + self.extra = extra or {} + self.extra_positional = extra_positional or [] + super().__init__( + "function arguments invalid." + f" ({len(self.missing)} missing," + f" {len(self.extra) + len(self.extra_positional)} additional)" + ) + + +class ImportStringError(ImportError): + """Provides information about a failed :func:`import_string` attempt.""" + + #: String in dotted notation that failed to be imported. + import_name: str + #: Wrapped exception. + exception: BaseException + + def __init__(self, import_name: str, exception: BaseException) -> None: + self.import_name = import_name + self.exception = exception + msg = import_name + name = "" + tracked = [] + for part in import_name.replace(":", ".").split("."): + name = f"{name}.{part}" if name else part + imported = import_string(name, silent=True) + if imported: + tracked.append((name, getattr(imported, "__file__", None))) + else: + track = [f"- {n!r} found in {i!r}." for n, i in tracked] + track.append(f"- {name!r} not found.") + track_str = "\n".join(track) + msg = ( + f"import_string() failed for {import_name!r}. Possible reasons" + f" are:\n\n" + "- missing __init__.py in a package;\n" + "- package or module path not included in sys.path;\n" + "- duplicated package or module name taking precedence in" + " sys.path;\n" + "- missing module, class, function or variable;\n\n" + f"Debugged import:\n\n{track_str}\n\n" + f"Original exception:\n\n{type(exception).__name__}: {exception}" + ) + break + + super().__init__(msg) + + def __repr__(self) -> str: + return f"<{type(self).__name__}({self.import_name!r}, {self.exception!r})>" diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/wrappers/__init__.py b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/wrappers/__init__.py new file mode 100644 index 000000000..eb69a9949 --- /dev/null +++ b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/wrappers/__init__.py @@ -0,0 +1,16 @@ +from .accept import AcceptMixin +from .auth import AuthorizationMixin +from .auth import WWWAuthenticateMixin +from .base_request import BaseRequest +from .base_response import BaseResponse +from .common_descriptors import CommonRequestDescriptorsMixin +from .common_descriptors import CommonResponseDescriptorsMixin +from .etag import ETagRequestMixin +from .etag import ETagResponseMixin +from .request import PlainRequest +from .request import Request as Request +from .request import StreamOnlyMixin +from .response import Response as Response +from .response import ResponseStream +from .response import ResponseStreamMixin +from .user_agent import UserAgentMixin diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/wrappers/__pycache__/__init__.cpython-38.pyc b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/wrappers/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..18660828204d65dca70e3c94eaff648bcd3d6679 GIT binary patch literal 926 zcmaKq$&S-N5Qgo|w1x{4AV+JHdTP}U|cU5({+*g*>QDDu!eZZemTsEF zQA)sn@G@pBj`jhsNb2!0$zq;X`Nvg2nevZ_-s?*o>Rx1=VD>JeH&wKLPJVZ3S6qfc?!n(mm1JL~gwF#Qf>hp)?-xxhr^m$wDKrct;&ni- zfkEaHrsMK*JiH+!YcG|D^QO=So}7~F-_3`&L=hcKp^*#+ye)ijM~hZf&k&L+j3soP z@4txKLhB0C6C0fzx^}T>oA*KZqVSQ}6y_&)5*}{gk2)L7osTi~2~VBr(%C)Ut$1)c zJ>9N@Gk>=BPoi}FWXB%C3L@vyNy223M+x?>i9gw{2%(c~dNNy{k&Uw@{Q8#UfwSc} hNkm204Wlq~-G|9dp+9A@pHuQo+`#l0jH&+b`2`b!1M2_) literal 0 HcmV?d00001 diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/wrappers/__pycache__/accept.cpython-38.pyc b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/wrappers/__pycache__/accept.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1d53cb72aabd81b078cbdae3453c835c9f227968 GIT binary patch literal 828 zcmYjPO>Yx15VhBvbSW)gg1f~f3R1JQY73PRq7o7(df-D9xmervB;CYuwzfB$RK2xQ zPw)%)13mDEeBr>Ue*rFxlc>r_GoD#n&+onU*Uim!f^qQsTlR|*@+SI4e8W~H7@r7H=)wsRoUx#KCa5>T?i=dw z36ZPJS|M)8-!d&tF+nxyBr|DmtRxtl?wzF5SJP9|-`(4-2mO6{Fp%5Y4qi`3FERQ$ z{gReNxmD>B8G_uZPXW}H+uJ@k*@sD5FC9p0la|b;_C9Ko(6*8yZ4m8nK8* z|C(#(Pgkt&+(}pqa&$HVA^f@!qp_@1=r@Eot1`9Rq0X~Iy+HSyONBWoC4_l|_bkeT y&^-<%SR9^%4NXGlb367;H!IO&^DLZ6LJ9jT2y?7};FC8CDsRIpv%%YhChR}UDcQ>a literal 0 HcmV?d00001 diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/wrappers/__pycache__/auth.cpython-38.pyc b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/wrappers/__pycache__/auth.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8d91ee3d29d592ddcfd8c45e5945735f484e843c GIT binary patch literal 1302 zcmb_c&2G~`5Z<+&)RdG$RXBhuSzIEJ8n>t|R6>YKNSx?_9|dx;T<<2?bhCDM*Krb+ zQ!DiZPrwWGm3-mAsjmPRW|F9i)E*G(N;A8&BhSqD&3Hd8EiDpQyFb6gpIt(Jp)tSU zj=Tmj8=x3rRFW2*>`u#hOPIslQ^MR6r{#j>u?kofuslXTkSEm(_=@Z&sOOhAGt5&s z@JYm$!`+^T!YFfoxM!qInajM>TB~wKJkqK{yh^#Um)mZt#W=PiRqw=5sQq)d^kUsH zW%aO_?wZ?NtAx(wL?9KAWf1cilqEgNsC5v+HZXblh z=fke-F(3OQRZx}3{(yS=%4u)_yoF=HSs zbeus&CXAtbjclZ`3sn$P14XDu9oM1GpX$Q+(N(@1mpB#>lZyL1ips?(>ZdH1c&6q@00a?Kk(k!MPV5LrxXK-~m{IZr5lnbzM0nCWbz>j)z6f9+NG5#| zzGQJA=ws4uor5NFa83c+)CNDw=an>Niqh5L!sY=?a|=M$R%1uHLnb{zU)Q$JSnPu& z00{$$Cw!2EA^c#y+JpHJrui74 zBtt4_SzCMQQc}7nv}C6aSYQXogYEAUBbx_oN12I)EVN9MQi>?aMKsHlih45Aay*%W zEX$*3Qjd;h)sJp>c018iBctuiES896;mU44YOH6I{Mq8ju3aeC7*Pm;3q~})@(t1N!_RNiZlSX&INhBlx_dZL zLTc09>vfOr9SwB<&fcBbuzy$V55;b7hWC%gw_x=1?vrkj6+2a4fKO8F%mz}bopNt? zzjs$oyR+t@-I-2`0yXv^SX?jC-6`hw8t2Q0r|GJS#cJ zha0I5^=I%T{W$>ijuz*2C*=I1Zgd1^pTfMT6c)NTWudtyuR#PJUdQrCDV8RVjETo~c?q1#c6QB>j>;M1& literal 0 HcmV?d00001 diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/wrappers/__pycache__/base_response.cpython-38.pyc b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/wrappers/__pycache__/base_response.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ee9540257399ce2a6a2281aea6c65bdfbf1b135e GIT binary patch literal 1792 zcmd5-PjA~c6elH0mNRE-gLWPE55h3O_0ZVO(6kLO4Bc96w;a|M#e5-Tk#S_xl1fT; z9ORY&J++^pr``IMc-dj6e1&a?y(deHy9~pCT}FUEi;v`ckMF(T^V7}E4FY5D=O3e= z7$LvlVtx3qcmUHp0uV${MTWHcb%u^`gsa>c8!}41CxQv@j0jJ9r|!^)wJ%z*Zb>G6 z5xiu>mY|PGuYC@h$o@G6>`@#1AWfd7M#d;!A1*8&z%;i3WMy{fNO!`dC+O?S_!%4e zAZdYwfy5I&NCMFUGlAXW-$hgTxE#eQGUnl-jHf-v2BsERU(2G@Y3zXdX5hC6^C3+0 zAwWSURM4Wb_R^)Ka8GE#P93no_KzpsZzCg{7j(nKghJ+8#!(?f7^NayBua%N8EQGp z<{(Sb@R`)pV_A;FFZ(0NzLT`(d;&iaXNT1$di1#Ompx_itWXrlxn-! z-Ps%6mGi-(e(7MLqdZ5ABM2AQb;kSo(q84fd3uZ);yeIQeFIV?_Z5wc&Ml;E|-QawdiBjQrhx6xUr0N}7SoUO@OUp)C zrZif#?lXx!-dwi!@Ngp8`k5W>p#3ewu--KsKPC<}-lHtUo0ac0x~_SCm2< zT(Q6W@9gh3Oz(ziwg9rnsua?M#i0xj$}}#3$B{~kWhfG8D}`G2|EX9!Qv%H}ZAqFG zoa57tR0sMKc$EGW06IvU^Qt3qeo;C)guRbpUKEQ9%=^u69baD$CUCCS+|t+KxPBku z2EqpjA0dPQcC)q;OQjl1RUKe_HI}PJdw9WMYYS+#hJS?iOhSvpo()ahFP1sAsrYQ$v# literal 0 HcmV?d00001 diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/wrappers/__pycache__/common_descriptors.cpython-38.pyc b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/wrappers/__pycache__/common_descriptors.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cf0906682aed621428252eec43d326d70f75baf1 GIT binary patch literal 1379 zcmb_cOK%e~5VrS`hLZBAfCEy|iUScy&9Ug)dH;<|( zTGSK#0{%e%(q1@l>R*5hGn+Ju)T$tar5Vq7v@`x@zV(N>xmf~h{rku0lS{}Cbfy=a zkryCl2^1%sO46j|?lhg(gge|lBHTT6nl5-A_rdeQ^EiD+9#&4_E3z4*o?hPAF^|9y zWyYo>+>VFBDR;iP$D~EM%e|vo(?2F2X;vVgUo5;zyWLd16??fbc0-t0Cz(yPd6Nth zwRz$eUZmTmsOzCH92Z;%|$h3$fD|iqo9`q9_gKZIL(M@}BmZ)G$=-qvhw}Z!xz!S)8<*;$;z=cm}F>7oT?@G}V^29HyfL0|6fO}{a z^Bd!fK!UANsPIjm30+hfOH^VR!(JI7TNeLs7wNE- zN-nesSGU8#lfjN|udJ^0JMA^T-r>v2be;~nOR(B1d>>{>wwS97ZYcO-e@6(pXjhll zx7S23?2kJT_H~rOjZ7GqGi8AMY?_U1sBt_M5K{w1s7D>wq0X<$%>IK*2ho@SoI$WE z>I%k+S;o34&n4P5#`f|^jw93-?n^bQSImv^$3jZR^ew17QuAotMuh{wZW%PHwDkWY z!|$DeGg+#PI7e}f^!*=vKA!OT;!6KY=ux)6XSALHK)Q+w-y(ejRoU*c`(@9O3wU&% z3x5DHA%nhyX)l`R0t9Yi?(sa)hnJ>ehZ+1)`YwKV6&1!cMyJwDu2@u`jrjE(pBjV2 KtUSe1W{uTs(NnE9k|logplV2az;XbypWtRo_=N{dr+wp1|7v@g-{6g#1KfcEKHa z4WidTF~X=IZ93hpw)LJci`l1y*(X-p2FqbCST0x&qr2ou?E=0ctr+#}aw$`nMI+WSWIjy(dGvbhT4dS`Q;@giqyF2_S=h}QoMu}{l+l3RUt}bea z!*W;Lm61ry*k=jEHDcVnu%O0sTFh$`#fO3qxHxwU4*<$U0#*l4#qztV5D2hWx(Zk4 z8CQiDhKWo}7$QBIi;hCh6>P$6fl!voI6s@YuM-rwSbU|2B_3{{jt zMLLL6rJLDUVGn8`x(3k|$-}E`H!o4lAtD*|c^DS+Vc1VuF7R9r!=pSB zm4}+bek02i#X$Wfq%^SJ#`oa*dSBE~^I^>2HmC1db&P tS2YT4cnxZ1oWcK~R`9#qsL-#_I}_%zs=|9&4$dol8#E4~M?L#a{SSPPKnef= literal 0 HcmV?d00001 diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/wrappers/__pycache__/etag.cpython-38.pyc b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/wrappers/__pycache__/etag.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9360407dbc6c28bee76323dc20a9fab1bcb54987 GIT binary patch literal 1287 zcmb_c&1w`u5bmCz%@{YD1QiSjgO^1iJBcLGfQW+P$vtQ!xR+rv-8(y*-rh-f&;F5H z0y*gu_yT#Qxp?rDSI~=9J409!4aH%Ps=lvk`t#D#B7wF0<4e@C3Hgb}{DM33 z8boh^VuVpa+H|&CZRDF3#P;1AsD>fYrfMvHG?u1On`ruEN!M z##P~kVJcG-hDeX*qN9*=g&aWGTVWWBNNaGDUoyy4Is`PqFj7HhAQ)FV*ggox&&OSr zY;A9iddUvk?XgX%doRZQ4Ooc`_JSkAz;itekMc-V z9%>5vjVx0XOBKkN3lWCu9waN(DxU76LY9#djR%j;>>C``l@gE622%5KS`|*>O^oKW z8qK-j|2vyyh<_hWwE!5DhYBALwTx;;>Wt$Vv)E+3y58iqYouIuSv|l=e{0kuaI9ds ts!?deYfv+j4E_hThTq*rg?@$JxiFVi72eBoa9-itpm7L2>e+Yde*l)eK=1$n literal 0 HcmV?d00001 diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/wrappers/__pycache__/json.cpython-38.pyc b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/wrappers/__pycache__/json.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dbef88801eaca5fee42819063e1ba2d9d150f7c2 GIT binary patch literal 820 zcmYjPO=}x55S6rRcVm)%1bP>}7#HGgN|H8EN(+TTp)NEnp}vUCN_Mo7<&C8E+9tOI za%z7-=^w~n(xr!<@)t@E9ofcYU`8`L;_1D|e%;z?A&A4@-?E<=p+8}`8xag|Ang+< z9C55rA1_YQC+`s_Ji0L zFpqeAwb^f6qZsv@fYp4H3K2v-@M1M_tR#tp~^kPFrh z6_{g30@v>mMlQNUMSOuT$PbJ`Q()bjpSRcVw5ha|i_(ZZbAr=Mb6QKK=!u|4jK&lA zCN(`4=4>viA${K6?b7yVab5}Q+EkBgSm&zZ!cuoC=%CWMlVhDJ>1LEmz;$ZYy(Pe~ zgMG`oEI(6XA{59?K$(^rM8=`>+wWFH0N{AJ@(o*+!uW(Ssik8q_+o_`n4sMRt8cL1 zU@TXewT#`Mzd9|AoPe5il9_ZcR$LgH?w_R77t>QS+}q!)i{Sx3EcmXr#mnjF8N^Vh zU(!;RJC!bhq2N39sSs+%?e89*9EeF;uN+8gla;{8riC5rZaFiXz@Z6gLxXUP2_cyL zYc}WGYn!`k`uBnqos9%zzQx#R%qtc8O~%fvOszK9`RJ`)V*9O?x?Cv5n0W}VEX)1S yJqjh*8-4^EnglQAPVAd*R)W9gak!I&683ix7FYqnC$E-NUWfn72454LME?Q7Gu5{M literal 0 HcmV?d00001 diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/wrappers/__pycache__/request.cpython-38.pyc b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/wrappers/__pycache__/request.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8031f15eea0aea63dc2469a339d1c56034fa56c9 GIT binary patch literal 21279 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 Lx%6LU;`ILpnd18G literal 0 HcmV?d00001 diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/wrappers/__pycache__/user_agent.cpython-38.pyc b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/wrappers/__pycache__/user_agent.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fc80a9dc91c5f1af6635ef427caea1179ecab304 GIT binary patch literal 841 zcmY*X&2AGh5VqHwbgA0%BO$IUE>V!0rKl}bLWoL8oah0lDsr*5>q+XwakjQMn^e8E zQcv&%yg<*qjV~NH^%da4IEkuuq#4h6tnv4K_Tl>aDnZ-({Vo4R3HcL>yT-71k8Yo1 z5JXT#x^%uq-RL6`kzkiZu#2e61pPwV@eQ6M2Stc?EnXJ(1tu_`(T#u&;^2ac=qI}( zJt~-pFW0(_D-x4#6X!Mj#-RnX-v{kJ$*I%_ZRX?L^sR3W>&0roRYn&G|JDNR2Kqzv zIdn$`R8SWMz~31qj$M#Jd`{1!ACw}o0R7->(z<_HE0s1CP#P$52O`b2NNcH7dJL(7 z(RhM;Qm02?hG$Uq(^s9XPTKkmrxjS&O7*zLcA+W(HgzYE_9|UCIo7$7Zk7s(bDf%X zZX$%)rhe7BycjB&fI{L4($i8S(>P@Q$%iEn9B{N~`G&1ZFh1d2YUwx+;#g2U69h0p z^lR!La9*g~TF$S@Uze3ePLNGD$xYT9D*?u4JIC4d_4LH_w|BPdL4Q~54aAnVgE!OB zOSHbuzGkH?H!EEtLJ*tv34q#kJ6n6lyD-V>r2<)P@)8l*tio60d3d%?IWudBq={}r zLTF4Q7SZTmbLH&WeZ@PsOjZIbosWQXzsmV&EGiZ9HO^0~TrE4)dA6??7x?uh(E^m> z+&sdo7GWb~k3$GbhmT=HlF)hGj(yY3N|f3>4QG-NLjTT$`K&O(CvO)}-i7~WgTx6< F*njeV-&Ftr literal 0 HcmV?d00001 diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/wrappers/accept.py b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/wrappers/accept.py new file mode 100644 index 000000000..9605e637d --- /dev/null +++ b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/wrappers/accept.py @@ -0,0 +1,14 @@ +import typing as t +import warnings + + +class AcceptMixin: + def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: + warnings.warn( + "'AcceptMixin' is deprecated and will be removed in" + " Werkzeug 2.1. 'Request' now includes the functionality" + " directly.", + DeprecationWarning, + stacklevel=2, + ) + super().__init__(*args, **kwargs) # type: ignore diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/wrappers/auth.py b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/wrappers/auth.py new file mode 100644 index 000000000..da31b7cf7 --- /dev/null +++ b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/wrappers/auth.py @@ -0,0 +1,26 @@ +import typing as t +import warnings + + +class AuthorizationMixin: + def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: + warnings.warn( + "'AuthorizationMixin' is deprecated and will be removed in" + " Werkzeug 2.1. 'Request' now includes the functionality" + " directly.", + DeprecationWarning, + stacklevel=2, + ) + super().__init__(*args, **kwargs) # type: ignore + + +class WWWAuthenticateMixin: + def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: + warnings.warn( + "'WWWAuthenticateMixin' is deprecated and will be removed" + " in Werkzeug 2.1. 'Response' now includes the" + " functionality directly.", + DeprecationWarning, + stacklevel=2, + ) + super().__init__(*args, **kwargs) # type: ignore diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/wrappers/base_request.py b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/wrappers/base_request.py new file mode 100644 index 000000000..451989fd7 --- /dev/null +++ b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/wrappers/base_request.py @@ -0,0 +1,36 @@ +import typing as t +import warnings + +from .request import Request + + +class _FakeSubclassCheck(type): + def __subclasscheck__(cls, subclass: t.Type) -> bool: + warnings.warn( + "'BaseRequest' is deprecated and will be removed in" + " Werkzeug 2.1. Use 'issubclass(cls, Request)' instead.", + DeprecationWarning, + stacklevel=2, + ) + return issubclass(subclass, Request) + + def __instancecheck__(cls, instance: t.Any) -> bool: + warnings.warn( + "'BaseRequest' is deprecated and will be removed in" + " Werkzeug 2.1. Use 'isinstance(obj, Request)' instead.", + DeprecationWarning, + stacklevel=2, + ) + return isinstance(instance, Request) + + +class BaseRequest(Request, metaclass=_FakeSubclassCheck): + def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: + warnings.warn( + "'BaseRequest' is deprecated and will be removed in" + " Werkzeug 2.1. 'Request' now includes the functionality" + " directly.", + DeprecationWarning, + stacklevel=2, + ) + super().__init__(*args, **kwargs) diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/wrappers/base_response.py b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/wrappers/base_response.py new file mode 100644 index 000000000..3e0dc6766 --- /dev/null +++ b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/wrappers/base_response.py @@ -0,0 +1,36 @@ +import typing as t +import warnings + +from .response import Response + + +class _FakeSubclassCheck(type): + def __subclasscheck__(cls, subclass: t.Type) -> bool: + warnings.warn( + "'BaseResponse' is deprecated and will be removed in" + " Werkzeug 2.1. Use 'issubclass(cls, Response)' instead.", + DeprecationWarning, + stacklevel=2, + ) + return issubclass(subclass, Response) + + def __instancecheck__(cls, instance: t.Any) -> bool: + warnings.warn( + "'BaseResponse' is deprecated and will be removed in" + " Werkzeug 2.1. Use 'isinstance(obj, Response)' instead.", + DeprecationWarning, + stacklevel=2, + ) + return isinstance(instance, Response) + + +class BaseResponse(Response, metaclass=_FakeSubclassCheck): + def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: + warnings.warn( + "'BaseResponse' is deprecated and will be removed in" + " Werkzeug 2.1. 'Response' now includes the functionality" + " directly.", + DeprecationWarning, + stacklevel=2, + ) + super().__init__(*args, **kwargs) diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/wrappers/common_descriptors.py b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/wrappers/common_descriptors.py new file mode 100644 index 000000000..db87ea5fa --- /dev/null +++ b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/wrappers/common_descriptors.py @@ -0,0 +1,26 @@ +import typing as t +import warnings + + +class CommonRequestDescriptorsMixin: + def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: + warnings.warn( + "'CommonRequestDescriptorsMixin' is deprecated and will be" + " removed in Werkzeug 2.1. 'Request' now includes the" + " functionality directly.", + DeprecationWarning, + stacklevel=2, + ) + super().__init__(*args, **kwargs) # type: ignore + + +class CommonResponseDescriptorsMixin: + def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: + warnings.warn( + "'CommonResponseDescriptorsMixin' is deprecated and will be" + " removed in Werkzeug 2.1. 'Response' now includes the" + " functionality directly.", + DeprecationWarning, + stacklevel=2, + ) + super().__init__(*args, **kwargs) # type: ignore diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/wrappers/cors.py b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/wrappers/cors.py new file mode 100644 index 000000000..89cf83ef8 --- /dev/null +++ b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/wrappers/cors.py @@ -0,0 +1,26 @@ +import typing as t +import warnings + + +class CORSRequestMixin: + def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: + warnings.warn( + "'CORSRequestMixin' is deprecated and will be removed in" + " Werkzeug 2.1. 'Request' now includes the functionality" + " directly.", + DeprecationWarning, + stacklevel=2, + ) + super().__init__(*args, **kwargs) # type: ignore + + +class CORSResponseMixin: + def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: + warnings.warn( + "'CORSResponseMixin' is deprecated and will be removed in" + " Werkzeug 2.1. 'Response' now includes the functionality" + " directly.", + DeprecationWarning, + stacklevel=2, + ) + super().__init__(*args, **kwargs) # type: ignore diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/wrappers/etag.py b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/wrappers/etag.py new file mode 100644 index 000000000..2e9015a58 --- /dev/null +++ b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/wrappers/etag.py @@ -0,0 +1,26 @@ +import typing as t +import warnings + + +class ETagRequestMixin: + def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: + warnings.warn( + "'ETagRequestMixin' is deprecated and will be removed in" + " Werkzeug 2.1. 'Request' now includes the functionality" + " directly.", + DeprecationWarning, + stacklevel=2, + ) + super().__init__(*args, **kwargs) # type: ignore + + +class ETagResponseMixin: + def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: + warnings.warn( + "'ETagResponseMixin' is deprecated and will be removed in" + " Werkzeug 2.1. 'Response' now includes the functionality" + " directly.", + DeprecationWarning, + stacklevel=2, + ) + super().__init__(*args, **kwargs) # type: ignore diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/wrappers/json.py b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/wrappers/json.py new file mode 100644 index 000000000..ab6ed7ba9 --- /dev/null +++ b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/wrappers/json.py @@ -0,0 +1,13 @@ +import typing as t +import warnings + + +class JSONMixin: + def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: + warnings.warn( + "'JSONMixin' is deprecated and will be removed in Werkzeug" + " 2.1. 'Request' now includes the functionality directly.", + DeprecationWarning, + stacklevel=2, + ) + super().__init__(*args, **kwargs) # type: ignore diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/wrappers/request.py b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/wrappers/request.py new file mode 100644 index 000000000..60c3b5f4e --- /dev/null +++ b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/wrappers/request.py @@ -0,0 +1,660 @@ +import functools +import json +import typing +import typing as t +import warnings +from io import BytesIO + +from .._internal import _wsgi_decoding_dance +from ..datastructures import CombinedMultiDict +from ..datastructures import EnvironHeaders +from ..datastructures import FileStorage +from ..datastructures import ImmutableMultiDict +from ..datastructures import iter_multi_items +from ..datastructures import MultiDict +from ..formparser import default_stream_factory +from ..formparser import FormDataParser +from ..sansio.request import Request as _SansIORequest +from ..utils import cached_property +from ..utils import environ_property +from ..wsgi import _get_server +from ..wsgi import get_input_stream +from werkzeug.exceptions import BadRequest + +if t.TYPE_CHECKING: + import typing_extensions as te + from _typeshed.wsgi import WSGIApplication + from _typeshed.wsgi import WSGIEnvironment + + +class Request(_SansIORequest): + """Represents an incoming WSGI HTTP request, with headers and body + taken from the WSGI environment. Has properties and methods for + using the functionality defined by various HTTP specs. The data in + requests object is read-only. + + Text data is assumed to use UTF-8 encoding, which should be true for + the vast majority of modern clients. Using an encoding set by the + client is unsafe in Python due to extra encodings it provides, such + as ``zip``. To change the assumed encoding, subclass and replace + :attr:`charset`. + + :param environ: The WSGI environ is generated by the WSGI server and + contains information about the server configuration and client + request. + :param populate_request: Add this request object to the WSGI environ + as ``environ['werkzeug.request']``. Can be useful when + debugging. + :param shallow: Makes reading from :attr:`stream` (and any method + that would read from it) raise a :exc:`RuntimeError`. Useful to + prevent consuming the form data in middleware, which would make + it unavailable to the final application. + + .. versionchanged:: 2.0 + Combine ``BaseRequest`` and mixins into a single ``Request`` + class. Using the old classes is deprecated and will be removed + in Werkzeug 2.1. + + .. versionchanged:: 0.5 + Read-only mode is enforced with immutable classes for all data. + """ + + #: the maximum content length. This is forwarded to the form data + #: parsing function (:func:`parse_form_data`). When set and the + #: :attr:`form` or :attr:`files` attribute is accessed and the + #: parsing fails because more than the specified value is transmitted + #: a :exc:`~werkzeug.exceptions.RequestEntityTooLarge` exception is raised. + #: + #: Have a look at :doc:`/request_data` for more details. + #: + #: .. versionadded:: 0.5 + max_content_length: t.Optional[int] = None + + #: the maximum form field size. This is forwarded to the form data + #: parsing function (:func:`parse_form_data`). When set and the + #: :attr:`form` or :attr:`files` attribute is accessed and the + #: data in memory for post data is longer than the specified value a + #: :exc:`~werkzeug.exceptions.RequestEntityTooLarge` exception is raised. + #: + #: Have a look at :doc:`/request_data` for more details. + #: + #: .. versionadded:: 0.5 + max_form_memory_size: t.Optional[int] = None + + #: The form data parser that shoud be used. Can be replaced to customize + #: the form date parsing. + form_data_parser_class: t.Type[FormDataParser] = FormDataParser + + #: Disable the :attr:`data` property to avoid reading from the input + #: stream. + #: + #: .. deprecated:: 2.0 + #: Will be removed in Werkzeug 2.1. Create the request with + #: ``shallow=True`` instead. + #: + #: .. versionadded:: 0.9 + disable_data_descriptor: t.Optional[bool] = None + + #: The WSGI environment containing HTTP headers and information from + #: the WSGI server. + environ: "WSGIEnvironment" + + #: Set when creating the request object. If ``True``, reading from + #: the request body will cause a ``RuntimeException``. Useful to + #: prevent modifying the stream from middleware. + shallow: bool + + def __init__( + self, + environ: "WSGIEnvironment", + populate_request: bool = True, + shallow: bool = False, + ) -> None: + super().__init__( + method=environ.get("REQUEST_METHOD", "GET"), + scheme=environ.get("wsgi.url_scheme", "http"), + server=_get_server(environ), + root_path=_wsgi_decoding_dance( + environ.get("SCRIPT_NAME") or "", self.charset, self.encoding_errors + ), + path=_wsgi_decoding_dance( + environ.get("PATH_INFO") or "", self.charset, self.encoding_errors + ), + query_string=environ.get("QUERY_STRING", "").encode("latin1"), + headers=EnvironHeaders(environ), + remote_addr=environ.get("REMOTE_ADDR"), + ) + self.environ = environ + + if self.disable_data_descriptor is not None: + warnings.warn( + "'disable_data_descriptor' is deprecated and will be" + " removed in Werkzeug 2.1. Create the request with" + " 'shallow=True' instead.", + DeprecationWarning, + stacklevel=2, + ) + shallow = shallow or self.disable_data_descriptor + + self.shallow = shallow + + if populate_request and not shallow: + self.environ["werkzeug.request"] = self + + @classmethod + def from_values(cls, *args: t.Any, **kwargs: t.Any) -> "Request": + """Create a new request object based on the values provided. If + environ is given missing values are filled from there. This method is + useful for small scripts when you need to simulate a request from an URL. + Do not use this method for unittesting, there is a full featured client + object (:class:`Client`) that allows to create multipart requests, + support for cookies etc. + + This accepts the same options as the + :class:`~werkzeug.test.EnvironBuilder`. + + .. versionchanged:: 0.5 + This method now accepts the same arguments as + :class:`~werkzeug.test.EnvironBuilder`. Because of this the + `environ` parameter is now called `environ_overrides`. + + :return: request object + """ + from ..test import EnvironBuilder + + charset = kwargs.pop("charset", cls.charset) + kwargs["charset"] = charset + builder = EnvironBuilder(*args, **kwargs) + try: + return builder.get_request(cls) + finally: + builder.close() + + @classmethod + def application( + cls, f: t.Callable[["Request"], "WSGIApplication"] + ) -> "WSGIApplication": + """Decorate a function as responder that accepts the request as + the last argument. This works like the :func:`responder` + decorator but the function is passed the request object as the + last argument and the request object will be closed + automatically:: + + @Request.application + def my_wsgi_app(request): + return Response('Hello World!') + + As of Werkzeug 0.14 HTTP exceptions are automatically caught and + converted to responses instead of failing. + + :param f: the WSGI callable to decorate + :return: a new WSGI callable + """ + #: return a callable that wraps the -2nd argument with the request + #: and calls the function with all the arguments up to that one and + #: the request. The return value is then called with the latest + #: two arguments. This makes it possible to use this decorator for + #: both standalone WSGI functions as well as bound methods and + #: partially applied functions. + from ..exceptions import HTTPException + + @functools.wraps(f) + def application(*args): # type: ignore + request = cls(args[-2]) + with request: + try: + resp = f(*args[:-2] + (request,)) + except HTTPException as e: + resp = e.get_response(args[-2]) + return resp(*args[-2:]) + + return t.cast("WSGIApplication", application) + + def _get_file_stream( + self, + total_content_length: t.Optional[int], + content_type: t.Optional[str], + filename: t.Optional[str] = None, + content_length: t.Optional[int] = None, + ) -> t.BinaryIO: + """Called to get a stream for the file upload. + + This must provide a file-like class with `read()`, `readline()` + and `seek()` methods that is both writeable and readable. + + The default implementation returns a temporary file if the total + content length is higher than 500KB. Because many browsers do not + provide a content length for the files only the total content + length matters. + + :param total_content_length: the total content length of all the + data in the request combined. This value + is guaranteed to be there. + :param content_type: the mimetype of the uploaded file. + :param filename: the filename of the uploaded file. May be `None`. + :param content_length: the length of this file. This value is usually + not provided because webbrowsers do not provide + this value. + """ + return default_stream_factory( + total_content_length=total_content_length, + filename=filename, + content_type=content_type, + content_length=content_length, + ) + + @property + def want_form_data_parsed(self) -> bool: + """``True`` if the request method carries content. By default + this is true if a ``Content-Type`` is sent. + + .. versionadded:: 0.8 + """ + return bool(self.environ.get("CONTENT_TYPE")) + + def make_form_data_parser(self) -> FormDataParser: + """Creates the form data parser. Instantiates the + :attr:`form_data_parser_class` with some parameters. + + .. versionadded:: 0.8 + """ + return self.form_data_parser_class( + self._get_file_stream, + self.charset, + self.encoding_errors, + self.max_form_memory_size, + self.max_content_length, + self.parameter_storage_class, + ) + + def _load_form_data(self) -> None: + """Method used internally to retrieve submitted data. After calling + this sets `form` and `files` on the request object to multi dicts + filled with the incoming form data. As a matter of fact the input + stream will be empty afterwards. You can also call this method to + force the parsing of the form data. + + .. versionadded:: 0.8 + """ + # abort early if we have already consumed the stream + if "form" in self.__dict__: + return + + if self.want_form_data_parsed: + parser = self.make_form_data_parser() + data = parser.parse( + self._get_stream_for_parsing(), + self.mimetype, + self.content_length, + self.mimetype_params, + ) + else: + data = ( + self.stream, + self.parameter_storage_class(), + self.parameter_storage_class(), + ) + + # inject the values into the instance dict so that we bypass + # our cached_property non-data descriptor. + d = self.__dict__ + d["stream"], d["form"], d["files"] = data + + def _get_stream_for_parsing(self) -> t.BinaryIO: + """This is the same as accessing :attr:`stream` with the difference + that if it finds cached data from calling :meth:`get_data` first it + will create a new stream out of the cached data. + + .. versionadded:: 0.9.3 + """ + cached_data = getattr(self, "_cached_data", None) + if cached_data is not None: + return BytesIO(cached_data) + return self.stream + + def close(self) -> None: + """Closes associated resources of this request object. This + closes all file handles explicitly. You can also use the request + object in a with statement which will automatically close it. + + .. versionadded:: 0.9 + """ + files = self.__dict__.get("files") + for _key, value in iter_multi_items(files or ()): + value.close() + + def __enter__(self) -> "Request": + return self + + def __exit__(self, exc_type, exc_value, tb) -> None: # type: ignore + self.close() + + @cached_property + def stream(self) -> t.BinaryIO: + """ + If the incoming form data was not encoded with a known mimetype + the data is stored unmodified in this stream for consumption. Most + of the time it is a better idea to use :attr:`data` which will give + you that data as a string. The stream only returns the data once. + + Unlike :attr:`input_stream` this stream is properly guarded that you + can't accidentally read past the length of the input. Werkzeug will + internally always refer to this stream to read data which makes it + possible to wrap this object with a stream that does filtering. + + .. versionchanged:: 0.9 + This stream is now always available but might be consumed by the + form parser later on. Previously the stream was only set if no + parsing happened. + """ + if self.shallow: + raise RuntimeError( + "This request was created with 'shallow=True', reading" + " from the input stream is disabled." + ) + + return get_input_stream(self.environ) + + input_stream = environ_property[t.BinaryIO]( + "wsgi.input", + doc="""The WSGI input stream. + + In general it's a bad idea to use this one because you can + easily read past the boundary. Use the :attr:`stream` + instead.""", + ) + + @cached_property + def data(self) -> bytes: + """ + Contains the incoming request data as string in case it came with + a mimetype Werkzeug does not handle. + """ + return self.get_data(parse_form_data=True) + + @typing.overload + def get_data( # type: ignore + self, + cache: bool = True, + as_text: "te.Literal[False]" = False, + parse_form_data: bool = False, + ) -> bytes: + ... + + @typing.overload + def get_data( + self, + cache: bool = True, + as_text: "te.Literal[True]" = ..., + parse_form_data: bool = False, + ) -> str: + ... + + def get_data( + self, cache: bool = True, as_text: bool = False, parse_form_data: bool = False + ) -> t.Union[bytes, str]: + """This reads the buffered incoming data from the client into one + bytes object. By default this is cached but that behavior can be + changed by setting `cache` to `False`. + + Usually it's a bad idea to call this method without checking the + content length first as a client could send dozens of megabytes or more + to cause memory problems on the server. + + Note that if the form data was already parsed this method will not + return anything as form data parsing does not cache the data like + this method does. To implicitly invoke form data parsing function + set `parse_form_data` to `True`. When this is done the return value + of this method will be an empty string if the form parser handles + the data. This generally is not necessary as if the whole data is + cached (which is the default) the form parser will used the cached + data to parse the form data. Please be generally aware of checking + the content length first in any case before calling this method + to avoid exhausting server memory. + + If `as_text` is set to `True` the return value will be a decoded + string. + + .. versionadded:: 0.9 + """ + rv = getattr(self, "_cached_data", None) + if rv is None: + if parse_form_data: + self._load_form_data() + rv = self.stream.read() + if cache: + self._cached_data = rv + if as_text: + rv = rv.decode(self.charset, self.encoding_errors) + return rv # type: ignore + + @cached_property + def form(self) -> "ImmutableMultiDict[str, str]": + """The form parameters. By default an + :class:`~werkzeug.datastructures.ImmutableMultiDict` + is returned from this function. This can be changed by setting + :attr:`parameter_storage_class` to a different type. This might + be necessary if the order of the form data is important. + + Please keep in mind that file uploads will not end up here, but instead + in the :attr:`files` attribute. + + .. versionchanged:: 0.9 + + Previous to Werkzeug 0.9 this would only contain form data for POST + and PUT requests. + """ + self._load_form_data() + return self.form + + @cached_property + def values(self) -> "CombinedMultiDict[str, str]": + """A :class:`werkzeug.datastructures.CombinedMultiDict` that + combines :attr:`args` and :attr:`form`. + + For GET requests, only ``args`` are present, not ``form``. + + .. versionchanged:: 2.0 + For GET requests, only ``args`` are present, not ``form``. + """ + sources = [self.args] + + if self.method != "GET": + # GET requests can have a body, and some caching proxies + # might not treat that differently than a normal GET + # request, allowing form data to "invisibly" affect the + # cache without indication in the query string / URL. + sources.append(self.form) + + args = [] + + for d in sources: + if not isinstance(d, MultiDict): + d = MultiDict(d) + + args.append(d) + + return CombinedMultiDict(args) + + @cached_property + def files(self) -> "ImmutableMultiDict[str, FileStorage]": + """:class:`~werkzeug.datastructures.MultiDict` object containing + all uploaded files. Each key in :attr:`files` is the name from the + ````. Each value in :attr:`files` is a + Werkzeug :class:`~werkzeug.datastructures.FileStorage` object. + + It basically behaves like a standard file object you know from Python, + with the difference that it also has a + :meth:`~werkzeug.datastructures.FileStorage.save` function that can + store the file on the filesystem. + + Note that :attr:`files` will only contain data if the request method was + POST, PUT or PATCH and the ``

    `` that posted to the request had + ``enctype="multipart/form-data"``. It will be empty otherwise. + + See the :class:`~werkzeug.datastructures.MultiDict` / + :class:`~werkzeug.datastructures.FileStorage` documentation for + more details about the used data structure. + """ + self._load_form_data() + return self.files + + @property + def script_root(self) -> str: + """Alias for :attr:`self.root_path`. ``environ["SCRIPT_ROOT"]`` + without a trailing slash. + """ + return self.root_path + + @cached_property + def url_root(self) -> str: + """Alias for :attr:`root_url`. The URL with scheme, host, and + root path. For example, ``https://example.com/app/``. + """ + return self.root_url + + remote_user = environ_property[str]( + "REMOTE_USER", + doc="""If the server supports user authentication, and the + script is protected, this attribute contains the username the + user has authenticated as.""", + ) + is_multithread = environ_property[bool]( + "wsgi.multithread", + doc="""boolean that is `True` if the application is served by a + multithreaded WSGI server.""", + ) + is_multiprocess = environ_property[bool]( + "wsgi.multiprocess", + doc="""boolean that is `True` if the application is served by a + WSGI server that spawns multiple processes.""", + ) + is_run_once = environ_property[bool]( + "wsgi.run_once", + doc="""boolean that is `True` if the application will be + executed only once in a process lifetime. This is the case for + CGI for example, but it's not guaranteed that the execution only + happens one time.""", + ) + + # JSON + + #: A module or other object that has ``dumps`` and ``loads`` + #: functions that match the API of the built-in :mod:`json` module. + json_module = json + + @property + def json(self) -> t.Optional[t.Any]: + """The parsed JSON data if :attr:`mimetype` indicates JSON + (:mimetype:`application/json`, see :meth:`is_json`). + + Calls :meth:`get_json` with default arguments. + """ + return self.get_json() + + # Cached values for ``(silent=False, silent=True)``. Initialized + # with sentinel values. + _cached_json: t.Tuple[t.Any, t.Any] = (Ellipsis, Ellipsis) + + def get_json( + self, force: bool = False, silent: bool = False, cache: bool = True + ) -> t.Optional[t.Any]: + """Parse :attr:`data` as JSON. + + If the mimetype does not indicate JSON + (:mimetype:`application/json`, see :meth:`is_json`), this + returns ``None``. + + If parsing fails, :meth:`on_json_loading_failed` is called and + its return value is used as the return value. + + :param force: Ignore the mimetype and always try to parse JSON. + :param silent: Silence parsing errors and return ``None`` + instead. + :param cache: Store the parsed JSON to return for subsequent + calls. + """ + if cache and self._cached_json[silent] is not Ellipsis: + return self._cached_json[silent] + + if not (force or self.is_json): + return None + + data = self.get_data(cache=cache) + + try: + rv = self.json_module.loads(data) + except ValueError as e: + if silent: + rv = None + + if cache: + normal_rv, _ = self._cached_json + self._cached_json = (normal_rv, rv) + else: + rv = self.on_json_loading_failed(e) + + if cache: + _, silent_rv = self._cached_json + self._cached_json = (rv, silent_rv) + else: + if cache: + self._cached_json = (rv, rv) + + return rv + + def on_json_loading_failed(self, e: ValueError) -> t.Any: + """Called if :meth:`get_json` parsing fails and isn't silenced. + If this method returns a value, it is used as the return value + for :meth:`get_json`. The default implementation raises + :exc:`~werkzeug.exceptions.BadRequest`. + """ + raise BadRequest(f"Failed to decode JSON object: {e}") + + +class StreamOnlyMixin: + """Mixin to create a ``Request`` that disables the ``data``, + ``form``, and ``files`` properties. Only ``stream`` is available. + + .. deprecated:: 2.0 + Will be removed in Werkzeug 2.1. Create the request with + ``shallow=True`` instead. + + .. versionadded:: 0.9 + """ + + def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: + warnings.warn( + "'StreamOnlyMixin' is deprecated and will be removed in" + " Werkzeug 2.1. Create the request with 'shallow=True'" + " instead.", + DeprecationWarning, + stacklevel=2, + ) + kwargs["shallow"] = True + super().__init__(*args, **kwargs) # type: ignore + + +class PlainRequest(StreamOnlyMixin, Request): + """A request object without ``data``, ``form``, and ``files``. + + .. deprecated:: 2.0 + Will be removed in Werkzeug 2.1. Create the request with + ``shallow=True`` instead. + + .. versionadded:: 0.9 + """ + + def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: + warnings.warn( + "'PlainRequest' is deprecated and will be removed in" + " Werkzeug 2.1. Create the request with 'shallow=True'" + " instead.", + DeprecationWarning, + stacklevel=2, + ) + + # Don't show the DeprecationWarning for StreamOnlyMixin. + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + super().__init__(*args, **kwargs) diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/wrappers/response.py b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/wrappers/response.py new file mode 100644 index 000000000..a43c8bca3 --- /dev/null +++ b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/wrappers/response.py @@ -0,0 +1,890 @@ +import json +import typing +import typing as t +import warnings +from http import HTTPStatus + +from .._internal import _to_bytes +from ..datastructures import Headers +from ..http import remove_entity_headers +from ..sansio.response import Response as _SansIOResponse +from ..urls import iri_to_uri +from ..urls import url_join +from ..utils import cached_property +from ..wsgi import ClosingIterator +from ..wsgi import get_current_url +from werkzeug._internal import _get_environ +from werkzeug.http import generate_etag +from werkzeug.http import http_date +from werkzeug.http import is_resource_modified +from werkzeug.http import parse_etags +from werkzeug.http import parse_range_header +from werkzeug.wsgi import _RangeWrapper + +if t.TYPE_CHECKING: + import typing_extensions as te + from _typeshed.wsgi import StartResponse + from _typeshed.wsgi import WSGIApplication + from _typeshed.wsgi import WSGIEnvironment + + +def _warn_if_string(iterable: t.Iterable) -> None: + """Helper for the response objects to check if the iterable returned + to the WSGI server is not a string. + """ + if isinstance(iterable, str): + warnings.warn( + "Response iterable was set to a string. This will appear to" + " work but means that the server will send the data to the" + " client one character at a time. This is almost never" + " intended behavior, use 'response.data' to assign strings" + " to the response object.", + stacklevel=2, + ) + + +def _iter_encoded( + iterable: t.Iterable[t.Union[str, bytes]], charset: str +) -> t.Iterator[bytes]: + for item in iterable: + if isinstance(item, str): + yield item.encode(charset) + else: + yield item + + +def _clean_accept_ranges(accept_ranges: t.Union[bool, str]) -> str: + if accept_ranges is True: + return "bytes" + elif accept_ranges is False: + return "none" + elif isinstance(accept_ranges, str): + return accept_ranges + raise ValueError("Invalid accept_ranges value") + + +class Response(_SansIOResponse): + """Represents an outgoing WSGI HTTP response with body, status, and + headers. Has properties and methods for using the functionality + defined by various HTTP specs. + + The response body is flexible to support different use cases. The + simple form is passing bytes, or a string which will be encoded as + UTF-8. Passing an iterable of bytes or strings makes this a + streaming response. A generator is particularly useful for building + a CSV file in memory or using SSE (Server Sent Events). A file-like + object is also iterable, although the + :func:`~werkzeug.utils.send_file` helper should be used in that + case. + + The response object is itself a WSGI application callable. When + called (:meth:`__call__`) with ``environ`` and ``start_response``, + it will pass its status and headers to ``start_response`` then + return its body as an iterable. + + .. code-block:: python + + from werkzeug.wrappers.response import Response + + def index(): + return Response("Hello, World!") + + def application(environ, start_response): + path = environ.get("PATH_INFO") or "/" + + if path == "/": + response = index() + else: + response = Response("Not Found", status=404) + + return response(environ, start_response) + + :param response: The data for the body of the response. A string or + bytes, or tuple or list of strings or bytes, for a fixed-length + response, or any other iterable of strings or bytes for a + streaming response. Defaults to an empty body. + :param status: The status code for the response. Either an int, in + which case the default status message is added, or a string in + the form ``{code} {message}``, like ``404 Not Found``. Defaults + to 200. + :param headers: A :class:`~werkzeug.datastructures.Headers` object, + or a list of ``(key, value)`` tuples that will be converted to a + ``Headers`` object. + :param mimetype: The mime type (content type without charset or + other parameters) of the response. If the value starts with + ``text/`` (or matches some other special cases), the charset + will be added to create the ``content_type``. + :param content_type: The full content type of the response. + Overrides building the value from ``mimetype``. + :param direct_passthrough: Pass the response body directly through + as the WSGI iterable. This can be used when the body is a binary + file or other iterator of bytes, to skip some unnecessary + checks. Use :func:`~werkzeug.utils.send_file` instead of setting + this manually. + + .. versionchanged:: 2.0 + Combine ``BaseResponse`` and mixins into a single ``Response`` + class. Using the old classes is deprecated and will be removed + in Werkzeug 2.1. + + .. versionchanged:: 0.5 + The ``direct_passthrough`` parameter was added. + """ + + #: if set to `False` accessing properties on the response object will + #: not try to consume the response iterator and convert it into a list. + #: + #: .. versionadded:: 0.6.2 + #: + #: That attribute was previously called `implicit_seqence_conversion`. + #: (Notice the typo). If you did use this feature, you have to adapt + #: your code to the name change. + implicit_sequence_conversion = True + + #: Should this response object correct the location header to be RFC + #: conformant? This is true by default. + #: + #: .. versionadded:: 0.8 + autocorrect_location_header = True + + #: Should this response object automatically set the content-length + #: header if possible? This is true by default. + #: + #: .. versionadded:: 0.8 + automatically_set_content_length = True + + #: The response body to send as the WSGI iterable. A list of strings + #: or bytes represents a fixed-length response, any other iterable + #: is a streaming response. Strings are encoded to bytes as UTF-8. + #: + #: Do not set to a plain string or bytes, that will cause sending + #: the response to be very inefficient as it will iterate one byte + #: at a time. + response: t.Union[t.Iterable[str], t.Iterable[bytes]] + + def __init__( + self, + response: t.Optional[ + t.Union[t.Iterable[bytes], bytes, t.Iterable[str], str] + ] = None, + status: t.Optional[t.Union[int, str, HTTPStatus]] = None, + headers: t.Optional[ + t.Union[ + t.Mapping[str, t.Union[str, int, t.Iterable[t.Union[str, int]]]], + t.Iterable[t.Tuple[str, t.Union[str, int]]], + ] + ] = None, + mimetype: t.Optional[str] = None, + content_type: t.Optional[str] = None, + direct_passthrough: bool = False, + ) -> None: + super().__init__( + status=status, + headers=headers, + mimetype=mimetype, + content_type=content_type, + ) + + #: Pass the response body directly through as the WSGI iterable. + #: This can be used when the body is a binary file or other + #: iterator of bytes, to skip some unnecessary checks. Use + #: :func:`~werkzeug.utils.send_file` instead of setting this + #: manually. + self.direct_passthrough = direct_passthrough + self._on_close: t.List[t.Callable[[], t.Any]] = [] + + # we set the response after the headers so that if a class changes + # the charset attribute, the data is set in the correct charset. + if response is None: + self.response = [] + elif isinstance(response, (str, bytes, bytearray)): + self.set_data(response) + else: + self.response = response + + def call_on_close(self, func: t.Callable[[], t.Any]) -> t.Callable[[], t.Any]: + """Adds a function to the internal list of functions that should + be called as part of closing down the response. Since 0.7 this + function also returns the function that was passed so that this + can be used as a decorator. + + .. versionadded:: 0.6 + """ + self._on_close.append(func) + return func + + def __repr__(self) -> str: + if self.is_sequence: + body_info = f"{sum(map(len, self.iter_encoded()))} bytes" + else: + body_info = "streamed" if self.is_streamed else "likely-streamed" + return f"<{type(self).__name__} {body_info} [{self.status}]>" + + @classmethod + def force_type( + cls, response: "Response", environ: t.Optional["WSGIEnvironment"] = None + ) -> "Response": + """Enforce that the WSGI response is a response object of the current + type. Werkzeug will use the :class:`Response` internally in many + situations like the exceptions. If you call :meth:`get_response` on an + exception you will get back a regular :class:`Response` object, even + if you are using a custom subclass. + + This method can enforce a given response type, and it will also + convert arbitrary WSGI callables into response objects if an environ + is provided:: + + # convert a Werkzeug response object into an instance of the + # MyResponseClass subclass. + response = MyResponseClass.force_type(response) + + # convert any WSGI application into a response object + response = MyResponseClass.force_type(response, environ) + + This is especially useful if you want to post-process responses in + the main dispatcher and use functionality provided by your subclass. + + Keep in mind that this will modify response objects in place if + possible! + + :param response: a response object or wsgi application. + :param environ: a WSGI environment object. + :return: a response object. + """ + if not isinstance(response, Response): + if environ is None: + raise TypeError( + "cannot convert WSGI application into response" + " objects without an environ" + ) + + from ..test import run_wsgi_app + + response = Response(*run_wsgi_app(response, environ)) + + response.__class__ = cls + return response + + @classmethod + def from_app( + cls, app: "WSGIApplication", environ: "WSGIEnvironment", buffered: bool = False + ) -> "Response": + """Create a new response object from an application output. This + works best if you pass it an application that returns a generator all + the time. Sometimes applications may use the `write()` callable + returned by the `start_response` function. This tries to resolve such + edge cases automatically. But if you don't get the expected output + you should set `buffered` to `True` which enforces buffering. + + :param app: the WSGI application to execute. + :param environ: the WSGI environment to execute against. + :param buffered: set to `True` to enforce buffering. + :return: a response object. + """ + from ..test import run_wsgi_app + + return cls(*run_wsgi_app(app, environ, buffered)) + + @typing.overload + def get_data(self, as_text: "te.Literal[False]" = False) -> bytes: + ... + + @typing.overload + def get_data(self, as_text: "te.Literal[True]") -> str: + ... + + def get_data(self, as_text: bool = False) -> t.Union[bytes, str]: + """The string representation of the response body. Whenever you call + this property the response iterable is encoded and flattened. This + can lead to unwanted behavior if you stream big data. + + This behavior can be disabled by setting + :attr:`implicit_sequence_conversion` to `False`. + + If `as_text` is set to `True` the return value will be a decoded + string. + + .. versionadded:: 0.9 + """ + self._ensure_sequence() + rv = b"".join(self.iter_encoded()) + + if as_text: + return rv.decode(self.charset) + + return rv + + def set_data(self, value: t.Union[bytes, str]) -> None: + """Sets a new string as response. The value must be a string or + bytes. If a string is set it's encoded to the charset of the + response (utf-8 by default). + + .. versionadded:: 0.9 + """ + # if a string is set, it's encoded directly so that we + # can set the content length + if isinstance(value, str): + value = value.encode(self.charset) + else: + value = bytes(value) + self.response = [value] + if self.automatically_set_content_length: + self.headers["Content-Length"] = str(len(value)) + + data = property( + get_data, + set_data, + doc="A descriptor that calls :meth:`get_data` and :meth:`set_data`.", + ) + + def calculate_content_length(self) -> t.Optional[int]: + """Returns the content length if available or `None` otherwise.""" + try: + self._ensure_sequence() + except RuntimeError: + return None + return sum(len(x) for x in self.iter_encoded()) + + def _ensure_sequence(self, mutable: bool = False) -> None: + """This method can be called by methods that need a sequence. If + `mutable` is true, it will also ensure that the response sequence + is a standard Python list. + + .. versionadded:: 0.6 + """ + if self.is_sequence: + # if we need a mutable object, we ensure it's a list. + if mutable and not isinstance(self.response, list): + self.response = list(self.response) # type: ignore + return + if self.direct_passthrough: + raise RuntimeError( + "Attempted implicit sequence conversion but the" + " response object is in direct passthrough mode." + ) + if not self.implicit_sequence_conversion: + raise RuntimeError( + "The response object required the iterable to be a" + " sequence, but the implicit conversion was disabled." + " Call make_sequence() yourself." + ) + self.make_sequence() + + def make_sequence(self) -> None: + """Converts the response iterator in a list. By default this happens + automatically if required. If `implicit_sequence_conversion` is + disabled, this method is not automatically called and some properties + might raise exceptions. This also encodes all the items. + + .. versionadded:: 0.6 + """ + if not self.is_sequence: + # if we consume an iterable we have to ensure that the close + # method of the iterable is called if available when we tear + # down the response + close = getattr(self.response, "close", None) + self.response = list(self.iter_encoded()) + if close is not None: + self.call_on_close(close) + + def iter_encoded(self) -> t.Iterator[bytes]: + """Iter the response encoded with the encoding of the response. + If the response object is invoked as WSGI application the return + value of this method is used as application iterator unless + :attr:`direct_passthrough` was activated. + """ + if __debug__: + _warn_if_string(self.response) + # Encode in a separate function so that self.response is fetched + # early. This allows us to wrap the response with the return + # value from get_app_iter or iter_encoded. + return _iter_encoded(self.response, self.charset) + + @property + def is_streamed(self) -> bool: + """If the response is streamed (the response is not an iterable with + a length information) this property is `True`. In this case streamed + means that there is no information about the number of iterations. + This is usually `True` if a generator is passed to the response object. + + This is useful for checking before applying some sort of post + filtering that should not take place for streamed responses. + """ + try: + len(self.response) # type: ignore + except (TypeError, AttributeError): + return True + return False + + @property + def is_sequence(self) -> bool: + """If the iterator is buffered, this property will be `True`. A + response object will consider an iterator to be buffered if the + response attribute is a list or tuple. + + .. versionadded:: 0.6 + """ + return isinstance(self.response, (tuple, list)) + + def close(self) -> None: + """Close the wrapped response if possible. You can also use the object + in a with statement which will automatically close it. + + .. versionadded:: 0.9 + Can now be used in a with statement. + """ + if hasattr(self.response, "close"): + self.response.close() # type: ignore + for func in self._on_close: + func() + + def __enter__(self) -> "Response": + return self + + def __exit__(self, exc_type, exc_value, tb): # type: ignore + self.close() + + def freeze(self, no_etag: None = None) -> None: + """Make the response object ready to be pickled. Does the + following: + + * Buffer the response into a list, ignoring + :attr:`implicity_sequence_conversion` and + :attr:`direct_passthrough`. + * Set the ``Content-Length`` header. + * Generate an ``ETag`` header if one is not already set. + + .. versionchanged:: 2.0 + An ``ETag`` header is added, the ``no_etag`` parameter is + deprecated and will be removed in Werkzeug 2.1. + + .. versionchanged:: 0.6 + The ``Content-Length`` header is set. + """ + # Always freeze the encoded response body, ignore + # implicit_sequence_conversion and direct_passthrough. + self.response = list(self.iter_encoded()) + self.headers["Content-Length"] = str(sum(map(len, self.response))) + + if no_etag is not None: + warnings.warn( + "The 'no_etag' parameter is deprecated and will be" + " removed in Werkzeug 2.1.", + DeprecationWarning, + stacklevel=2, + ) + + self.add_etag() + + def get_wsgi_headers(self, environ: "WSGIEnvironment") -> Headers: + """This is automatically called right before the response is started + and returns headers modified for the given environment. It returns a + copy of the headers from the response with some modifications applied + if necessary. + + For example the location header (if present) is joined with the root + URL of the environment. Also the content length is automatically set + to zero here for certain status codes. + + .. versionchanged:: 0.6 + Previously that function was called `fix_headers` and modified + the response object in place. Also since 0.6, IRIs in location + and content-location headers are handled properly. + + Also starting with 0.6, Werkzeug will attempt to set the content + length if it is able to figure it out on its own. This is the + case if all the strings in the response iterable are already + encoded and the iterable is buffered. + + :param environ: the WSGI environment of the request. + :return: returns a new :class:`~werkzeug.datastructures.Headers` + object. + """ + headers = Headers(self.headers) + location: t.Optional[str] = None + content_location: t.Optional[str] = None + content_length: t.Optional[t.Union[str, int]] = None + status = self.status_code + + # iterate over the headers to find all values in one go. Because + # get_wsgi_headers is used each response that gives us a tiny + # speedup. + for key, value in headers: + ikey = key.lower() + if ikey == "location": + location = value + elif ikey == "content-location": + content_location = value + elif ikey == "content-length": + content_length = value + + # make sure the location header is an absolute URL + if location is not None: + old_location = location + if isinstance(location, str): + # Safe conversion is necessary here as we might redirect + # to a broken URI scheme (for instance itms-services). + location = iri_to_uri(location, safe_conversion=True) + + if self.autocorrect_location_header: + current_url = get_current_url(environ, strip_querystring=True) + if isinstance(current_url, str): + current_url = iri_to_uri(current_url) + location = url_join(current_url, location) + if location != old_location: + headers["Location"] = location + + # make sure the content location is a URL + if content_location is not None and isinstance(content_location, str): + headers["Content-Location"] = iri_to_uri(content_location) + + if 100 <= status < 200 or status == 204: + # Per section 3.3.2 of RFC 7230, "a server MUST NOT send a + # Content-Length header field in any response with a status + # code of 1xx (Informational) or 204 (No Content)." + headers.remove("Content-Length") + elif status == 304: + remove_entity_headers(headers) + + # if we can determine the content length automatically, we + # should try to do that. But only if this does not involve + # flattening the iterator or encoding of strings in the + # response. We however should not do that if we have a 304 + # response. + if ( + self.automatically_set_content_length + and self.is_sequence + and content_length is None + and status not in (204, 304) + and not (100 <= status < 200) + ): + try: + content_length = sum(len(_to_bytes(x, "ascii")) for x in self.response) + except UnicodeError: + # Something other than bytes, can't safely figure out + # the length of the response. + pass + else: + headers["Content-Length"] = str(content_length) + + return headers + + def get_app_iter(self, environ: "WSGIEnvironment") -> t.Iterable[bytes]: + """Returns the application iterator for the given environ. Depending + on the request method and the current status code the return value + might be an empty response rather than the one from the response. + + If the request method is `HEAD` or the status code is in a range + where the HTTP specification requires an empty response, an empty + iterable is returned. + + .. versionadded:: 0.6 + + :param environ: the WSGI environment of the request. + :return: a response iterable. + """ + status = self.status_code + if ( + environ["REQUEST_METHOD"] == "HEAD" + or 100 <= status < 200 + or status in (204, 304) + ): + iterable: t.Iterable[bytes] = () + elif self.direct_passthrough: + if __debug__: + _warn_if_string(self.response) + return self.response # type: ignore + else: + iterable = self.iter_encoded() + return ClosingIterator(iterable, self.close) + + def get_wsgi_response( + self, environ: "WSGIEnvironment" + ) -> t.Tuple[t.Iterable[bytes], str, t.List[t.Tuple[str, str]]]: + """Returns the final WSGI response as tuple. The first item in + the tuple is the application iterator, the second the status and + the third the list of headers. The response returned is created + specially for the given environment. For example if the request + method in the WSGI environment is ``'HEAD'`` the response will + be empty and only the headers and status code will be present. + + .. versionadded:: 0.6 + + :param environ: the WSGI environment of the request. + :return: an ``(app_iter, status, headers)`` tuple. + """ + headers = self.get_wsgi_headers(environ) + app_iter = self.get_app_iter(environ) + return app_iter, self.status, headers.to_wsgi_list() + + def __call__( + self, environ: "WSGIEnvironment", start_response: "StartResponse" + ) -> t.Iterable[bytes]: + """Process this response as WSGI application. + + :param environ: the WSGI environment. + :param start_response: the response callable provided by the WSGI + server. + :return: an application iterator + """ + app_iter, status, headers = self.get_wsgi_response(environ) + start_response(status, headers) + return app_iter + + # JSON + + #: A module or other object that has ``dumps`` and ``loads`` + #: functions that match the API of the built-in :mod:`json` module. + json_module = json + + @property + def json(self) -> t.Optional[t.Any]: + """The parsed JSON data if :attr:`mimetype` indicates JSON + (:mimetype:`application/json`, see :meth:`is_json`). + + Calls :meth:`get_json` with default arguments. + """ + return self.get_json() + + def get_json(self, force: bool = False, silent: bool = False) -> t.Optional[t.Any]: + """Parse :attr:`data` as JSON. Useful during testing. + + If the mimetype does not indicate JSON + (:mimetype:`application/json`, see :meth:`is_json`), this + returns ``None``. + + Unlike :meth:`Request.get_json`, the result is not cached. + + :param force: Ignore the mimetype and always try to parse JSON. + :param silent: Silence parsing errors and return ``None`` + instead. + """ + if not (force or self.is_json): + return None + + data = self.get_data() + + try: + return self.json_module.loads(data) + except ValueError: + if not silent: + raise + + return None + + # Stream + + @cached_property + def stream(self) -> "ResponseStream": + """The response iterable as write-only stream.""" + return ResponseStream(self) + + def _wrap_range_response(self, start: int, length: int) -> None: + """Wrap existing Response in case of Range Request context.""" + if self.status_code == 206: + self.response = _RangeWrapper(self.response, start, length) # type: ignore + + def _is_range_request_processable(self, environ: "WSGIEnvironment") -> bool: + """Return ``True`` if `Range` header is present and if underlying + resource is considered unchanged when compared with `If-Range` header. + """ + return ( + "HTTP_IF_RANGE" not in environ + or not is_resource_modified( + environ, + self.headers.get("etag"), + None, + self.headers.get("last-modified"), + ignore_if_range=False, + ) + ) and "HTTP_RANGE" in environ + + def _process_range_request( + self, + environ: "WSGIEnvironment", + complete_length: t.Optional[int] = None, + accept_ranges: t.Optional[t.Union[bool, str]] = None, + ) -> bool: + """Handle Range Request related headers (RFC7233). If `Accept-Ranges` + header is valid, and Range Request is processable, we set the headers + as described by the RFC, and wrap the underlying response in a + RangeWrapper. + + Returns ``True`` if Range Request can be fulfilled, ``False`` otherwise. + + :raises: :class:`~werkzeug.exceptions.RequestedRangeNotSatisfiable` + if `Range` header could not be parsed or satisfied. + + .. versionchanged:: 2.0 + Returns ``False`` if the length is 0. + """ + from ..exceptions import RequestedRangeNotSatisfiable + + if ( + accept_ranges is None + or complete_length is None + or complete_length == 0 + or not self._is_range_request_processable(environ) + ): + return False + + parsed_range = parse_range_header(environ.get("HTTP_RANGE")) + + if parsed_range is None: + raise RequestedRangeNotSatisfiable(complete_length) + + range_tuple = parsed_range.range_for_length(complete_length) + content_range_header = parsed_range.to_content_range_header(complete_length) + + if range_tuple is None or content_range_header is None: + raise RequestedRangeNotSatisfiable(complete_length) + + content_length = range_tuple[1] - range_tuple[0] + self.headers["Content-Length"] = content_length + self.headers["Accept-Ranges"] = accept_ranges + self.content_range = content_range_header # type: ignore + self.status_code = 206 + self._wrap_range_response(range_tuple[0], content_length) + return True + + def make_conditional( + self, + request_or_environ: "WSGIEnvironment", + accept_ranges: t.Union[bool, str] = False, + complete_length: t.Optional[int] = None, + ) -> "Response": + """Make the response conditional to the request. This method works + best if an etag was defined for the response already. The `add_etag` + method can be used to do that. If called without etag just the date + header is set. + + This does nothing if the request method in the request or environ is + anything but GET or HEAD. + + For optimal performance when handling range requests, it's recommended + that your response data object implements `seekable`, `seek` and `tell` + methods as described by :py:class:`io.IOBase`. Objects returned by + :meth:`~werkzeug.wsgi.wrap_file` automatically implement those methods. + + It does not remove the body of the response because that's something + the :meth:`__call__` function does for us automatically. + + Returns self so that you can do ``return resp.make_conditional(req)`` + but modifies the object in-place. + + :param request_or_environ: a request object or WSGI environment to be + used to make the response conditional + against. + :param accept_ranges: This parameter dictates the value of + `Accept-Ranges` header. If ``False`` (default), + the header is not set. If ``True``, it will be set + to ``"bytes"``. If ``None``, it will be set to + ``"none"``. If it's a string, it will use this + value. + :param complete_length: Will be used only in valid Range Requests. + It will set `Content-Range` complete length + value and compute `Content-Length` real value. + This parameter is mandatory for successful + Range Requests completion. + :raises: :class:`~werkzeug.exceptions.RequestedRangeNotSatisfiable` + if `Range` header could not be parsed or satisfied. + + .. versionchanged:: 2.0 + Range processing is skipped if length is 0 instead of + raising a 416 Range Not Satisfiable error. + """ + environ = _get_environ(request_or_environ) + if environ["REQUEST_METHOD"] in ("GET", "HEAD"): + # if the date is not in the headers, add it now. We however + # will not override an already existing header. Unfortunately + # this header will be overriden by many WSGI servers including + # wsgiref. + if "date" not in self.headers: + self.headers["Date"] = http_date() + accept_ranges = _clean_accept_ranges(accept_ranges) + is206 = self._process_range_request(environ, complete_length, accept_ranges) + if not is206 and not is_resource_modified( + environ, + self.headers.get("etag"), + None, + self.headers.get("last-modified"), + ): + if parse_etags(environ.get("HTTP_IF_MATCH")): + self.status_code = 412 + else: + self.status_code = 304 + if ( + self.automatically_set_content_length + and "content-length" not in self.headers + ): + length = self.calculate_content_length() + if length is not None: + self.headers["Content-Length"] = length + return self + + def add_etag(self, overwrite: bool = False, weak: bool = False) -> None: + """Add an etag for the current response if there is none yet. + + .. versionchanged:: 2.0 + SHA-1 is used to generate the value. MD5 may not be + available in some environments. + """ + if overwrite or "etag" not in self.headers: + self.set_etag(generate_etag(self.get_data()), weak) + + +class ResponseStream: + """A file descriptor like object used by the :class:`ResponseStreamMixin` to + represent the body of the stream. It directly pushes into the response + iterable of the response object. + """ + + mode = "wb+" + + def __init__(self, response: Response): + self.response = response + self.closed = False + + def write(self, value: bytes) -> int: + if self.closed: + raise ValueError("I/O operation on closed file") + self.response._ensure_sequence(mutable=True) + self.response.response.append(value) # type: ignore + self.response.headers.pop("Content-Length", None) + return len(value) + + def writelines(self, seq: t.Iterable[bytes]) -> None: + for item in seq: + self.write(item) + + def close(self) -> None: + self.closed = True + + def flush(self) -> None: + if self.closed: + raise ValueError("I/O operation on closed file") + + def isatty(self) -> bool: + if self.closed: + raise ValueError("I/O operation on closed file") + return False + + def tell(self) -> int: + self.response._ensure_sequence() + return sum(map(len, self.response.response)) + + @property + def encoding(self) -> str: + return self.response.charset + + +class ResponseStreamMixin: + def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: + warnings.warn( + "'ResponseStreamMixin' is deprecated and will be removed in" + " Werkzeug 2.1. 'Response' now includes the functionality" + " directly.", + DeprecationWarning, + stacklevel=2, + ) + super().__init__(*args, **kwargs) # type: ignore diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/wrappers/user_agent.py b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/wrappers/user_agent.py new file mode 100644 index 000000000..184ffd023 --- /dev/null +++ b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/wrappers/user_agent.py @@ -0,0 +1,14 @@ +import typing as t +import warnings + + +class UserAgentMixin: + def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: + warnings.warn( + "'UserAgentMixin' is deprecated and will be removed in" + " Werkzeug 2.1. 'Request' now includes the functionality" + " directly.", + DeprecationWarning, + stacklevel=2, + ) + super().__init__(*args, **kwargs) # type: ignore diff --git a/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/wsgi.py b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/wsgi.py new file mode 100644 index 000000000..9439a1e5c --- /dev/null +++ b/BackEndChallenge/myproject/lib/python3.8/site-packages/werkzeug/wsgi.py @@ -0,0 +1,982 @@ +import io +import re +import typing as t +from functools import partial +from functools import update_wrapper +from itertools import chain + +from ._internal import _make_encode_wrapper +from ._internal import _to_bytes +from ._internal import _to_str +from .sansio import utils as _sansio_utils +from .sansio.utils import host_is_trusted # noqa: F401 # Imported as part of API +from .urls import _URLTuple +from .urls import uri_to_iri +from .urls import url_join +from .urls import url_parse +from .urls import url_quote + +if t.TYPE_CHECKING: + from _typeshed.wsgi import WSGIApplication + from _typeshed.wsgi import WSGIEnvironment + + +def responder(f: t.Callable[..., "WSGIApplication"]) -> "WSGIApplication": + """Marks a function as responder. Decorate a function with it and it + will automatically call the return value as WSGI application. + + Example:: + + @responder + def application(environ, start_response): + return Response('Hello World!') + """ + return update_wrapper(lambda *a: f(*a)(*a[-2:]), f) + + +def get_current_url( + environ: "WSGIEnvironment", + root_only: bool = False, + strip_querystring: bool = False, + host_only: bool = False, + trusted_hosts: t.Optional[t.Iterable[str]] = None, +) -> str: + """Recreate the URL for a request from the parts in a WSGI + environment. + + The URL is an IRI, not a URI, so it may contain Unicode characters. + Use :func:`~werkzeug.urls.iri_to_uri` to convert it to ASCII. + + :param environ: The WSGI environment to get the URL parts from. + :param root_only: Only build the root path, don't include the + remaining path or query string. + :param strip_querystring: Don't include the query string. + :param host_only: Only build the scheme and host. + :param trusted_hosts: A list of trusted host names to validate the + host against. + """ + parts = { + "scheme": environ["wsgi.url_scheme"], + "host": get_host(environ, trusted_hosts), + } + + if not host_only: + parts["root_path"] = environ.get("SCRIPT_NAME", "") + + if not root_only: + parts["path"] = environ.get("PATH_INFO", "") + + if not strip_querystring: + parts["query_string"] = environ.get("QUERY_STRING", "").encode("latin1") + + return _sansio_utils.get_current_url(**parts) + + +def _get_server( + environ: "WSGIEnvironment", +) -> t.Optional[t.Tuple[str, t.Optional[int]]]: + name = environ.get("SERVER_NAME") + + if name is None: + return None + + try: + port: t.Optional[int] = int(environ.get("SERVER_PORT", None)) + except (TypeError, ValueError): + # unix socket + port = None + + return name, port + + +def get_host( + environ: "WSGIEnvironment", trusted_hosts: t.Optional[t.Iterable[str]] = None +) -> str: + """Return the host for the given WSGI environment. + + The ``Host`` header is preferred, then ``SERVER_NAME`` if it's not + set. The returned host will only contain the port if it is different + than the standard port for the protocol. + + Optionally, verify that the host is trusted using + :func:`host_is_trusted` and raise a + :exc:`~werkzeug.exceptions.SecurityError` if it is not. + + :param environ: A WSGI environment dict. + :param trusted_hosts: A list of trusted host names. + + :return: Host, with port if necessary. + :raise ~werkzeug.exceptions.SecurityError: If the host is not + trusted. + """ + return _sansio_utils.get_host( + environ["wsgi.url_scheme"], + environ.get("HTTP_HOST"), + _get_server(environ), + trusted_hosts, + ) + + +def get_content_length(environ: "WSGIEnvironment") -> t.Optional[int]: + """Returns the content length from the WSGI environment as + integer. If it's not available or chunked transfer encoding is used, + ``None`` is returned. + + .. versionadded:: 0.9 + + :param environ: the WSGI environ to fetch the content length from. + """ + if environ.get("HTTP_TRANSFER_ENCODING", "") == "chunked": + return None + + content_length = environ.get("CONTENT_LENGTH") + if content_length is not None: + try: + return max(0, int(content_length)) + except (ValueError, TypeError): + pass + return None + + +def get_input_stream( + environ: "WSGIEnvironment", safe_fallback: bool = True +) -> t.BinaryIO: + """Returns the input stream from the WSGI environment and wraps it + in the most sensible way possible. The stream returned is not the + raw WSGI stream in most cases but one that is safe to read from + without taking into account the content length. + + If content length is not set, the stream will be empty for safety reasons. + If the WSGI server supports chunked or infinite streams, it should set + the ``wsgi.input_terminated`` value in the WSGI environ to indicate that. + + .. versionadded:: 0.9 + + :param environ: the WSGI environ to fetch the stream from. + :param safe_fallback: use an empty stream as a safe fallback when the + content length is not set. Disabling this allows infinite streams, + which can be a denial-of-service risk. + """ + stream = t.cast(t.BinaryIO, environ["wsgi.input"]) + content_length = get_content_length(environ) + + # A wsgi extension that tells us if the input is terminated. In + # that case we return the stream unchanged as we know we can safely + # read it until the end. + if environ.get("wsgi.input_terminated"): + return stream + + # If the request doesn't specify a content length, returning the stream is + # potentially dangerous because it could be infinite, malicious or not. If + # safe_fallback is true, return an empty stream instead for safety. + if content_length is None: + return io.BytesIO() if safe_fallback else stream + + # Otherwise limit the stream to the content length + return t.cast(t.BinaryIO, LimitedStream(stream, content_length)) + + +def get_query_string(environ: "WSGIEnvironment") -> str: + """Returns the ``QUERY_STRING`` from the WSGI environment. This also + takes care of the WSGI decoding dance. The string returned will be + restricted to ASCII characters. + + :param environ: WSGI environment to get the query string from. + + .. versionadded:: 0.9 + """ + qs = environ.get("QUERY_STRING", "").encode("latin1") + # QUERY_STRING really should be ascii safe but some browsers + # will send us some unicode stuff (I am looking at you IE). + # In that case we want to urllib quote it badly. + return url_quote(qs, safe=":&%=+$!*'(),") + + +def get_path_info( + environ: "WSGIEnvironment", charset: str = "utf-8", errors: str = "replace" +) -> str: + """Return the ``PATH_INFO`` from the WSGI environment and decode it + unless ``charset`` is ``None``. + + :param environ: WSGI environment to get the path from. + :param charset: The charset for the path info, or ``None`` if no + decoding should be performed. + :param errors: The decoding error handling. + + .. versionadded:: 0.9 + """ + path = environ.get("PATH_INFO", "").encode("latin1") + return _to_str(path, charset, errors, allow_none_charset=True) # type: ignore + + +def get_script_name( + environ: "WSGIEnvironment", charset: str = "utf-8", errors: str = "replace" +) -> str: + """Return the ``SCRIPT_NAME`` from the WSGI environment and decode + it unless `charset` is set to ``None``. + + :param environ: WSGI environment to get the path from. + :param charset: The charset for the path, or ``None`` if no decoding + should be performed. + :param errors: The decoding error handling. + + .. versionadded:: 0.9 + """ + path = environ.get("SCRIPT_NAME", "").encode("latin1") + return _to_str(path, charset, errors, allow_none_charset=True) # type: ignore + + +def pop_path_info( + environ: "WSGIEnvironment", charset: str = "utf-8", errors: str = "replace" +) -> t.Optional[str]: + """Removes and returns the next segment of `PATH_INFO`, pushing it onto + `SCRIPT_NAME`. Returns `None` if there is nothing left on `PATH_INFO`. + + If the `charset` is set to `None` bytes are returned. + + If there are empty segments (``'/foo//bar``) these are ignored but + properly pushed to the `SCRIPT_NAME`: + + >>> env = {'SCRIPT_NAME': '/foo', 'PATH_INFO': '/a/b'} + >>> pop_path_info(env) + 'a' + >>> env['SCRIPT_NAME'] + '/foo/a' + >>> pop_path_info(env) + 'b' + >>> env['SCRIPT_NAME'] + '/foo/a/b' + + .. versionadded:: 0.5 + + .. versionchanged:: 0.9 + The path is now decoded and a charset and encoding + parameter can be provided. + + :param environ: the WSGI environment that is modified. + :param charset: The ``encoding`` parameter passed to + :func:`bytes.decode`. + :param errors: The ``errors`` paramater passed to + :func:`bytes.decode`. + """ + path = environ.get("PATH_INFO") + if not path: + return None + + script_name = environ.get("SCRIPT_NAME", "") + + # shift multiple leading slashes over + old_path = path + path = path.lstrip("/") + if path != old_path: + script_name += "/" * (len(old_path) - len(path)) + + if "/" not in path: + environ["PATH_INFO"] = "" + environ["SCRIPT_NAME"] = script_name + path + rv = path.encode("latin1") + else: + segment, path = path.split("/", 1) + environ["PATH_INFO"] = f"/{path}" + environ["SCRIPT_NAME"] = script_name + segment + rv = segment.encode("latin1") + + return _to_str(rv, charset, errors, allow_none_charset=True) # type: ignore + + +def peek_path_info( + environ: "WSGIEnvironment", charset: str = "utf-8", errors: str = "replace" +) -> t.Optional[str]: + """Returns the next segment on the `PATH_INFO` or `None` if there + is none. Works like :func:`pop_path_info` without modifying the + environment: + + >>> env = {'SCRIPT_NAME': '/foo', 'PATH_INFO': '/a/b'} + >>> peek_path_info(env) + 'a' + >>> peek_path_info(env) + 'a' + + If the `charset` is set to `None` bytes are returned. + + .. versionadded:: 0.5 + + .. versionchanged:: 0.9 + The path is now decoded and a charset and encoding + parameter can be provided. + + :param environ: the WSGI environment that is checked. + """ + segments = environ.get("PATH_INFO", "").lstrip("/").split("/", 1) + if segments: + return _to_str( # type: ignore + segments[0].encode("latin1"), charset, errors, allow_none_charset=True + ) + return None + + +def extract_path_info( + environ_or_baseurl: t.Union[str, "WSGIEnvironment"], + path_or_url: t.Union[str, _URLTuple], + charset: str = "utf-8", + errors: str = "werkzeug.url_quote", + collapse_http_schemes: bool = True, +) -> t.Optional[str]: + """Extracts the path info from the given URL (or WSGI environment) and + path. The path info returned is a string. The URLs might also be IRIs. + + If the path info could not be determined, `None` is returned. + + Some examples: + + >>> extract_path_info('http://example.com/app', '/app/hello') + '/hello' + >>> extract_path_info('http://example.com/app', + ... 'https://example.com/app/hello') + '/hello' + >>> extract_path_info('http://example.com/app', + ... 'https://example.com/app/hello', + ... collapse_http_schemes=False) is None + True + + Instead of providing a base URL you can also pass a WSGI environment. + + :param environ_or_baseurl: a WSGI environment dict, a base URL or + base IRI. This is the root of the + application. + :param path_or_url: an absolute path from the server root, a + relative path (in which case it's the path info) + or a full URL. + :param charset: the charset for byte data in URLs + :param errors: the error handling on decode + :param collapse_http_schemes: if set to `False` the algorithm does + not assume that http and https on the + same server point to the same + resource. + + .. versionchanged:: 0.15 + The ``errors`` parameter defaults to leaving invalid bytes + quoted instead of replacing them. + + .. versionadded:: 0.6 + """ + + def _normalize_netloc(scheme: str, netloc: str) -> str: + parts = netloc.split("@", 1)[-1].split(":", 1) + port: t.Optional[str] + + if len(parts) == 2: + netloc, port = parts + if (scheme == "http" and port == "80") or ( + scheme == "https" and port == "443" + ): + port = None + else: + netloc = parts[0] + port = None + + if port is not None: + netloc += f":{port}" + + return netloc + + # make sure whatever we are working on is a IRI and parse it + path = uri_to_iri(path_or_url, charset, errors) + if isinstance(environ_or_baseurl, dict): + environ_or_baseurl = get_current_url(environ_or_baseurl, root_only=True) + base_iri = uri_to_iri(environ_or_baseurl, charset, errors) + base_scheme, base_netloc, base_path = url_parse(base_iri)[:3] + cur_scheme, cur_netloc, cur_path = url_parse(url_join(base_iri, path))[:3] + + # normalize the network location + base_netloc = _normalize_netloc(base_scheme, base_netloc) + cur_netloc = _normalize_netloc(cur_scheme, cur_netloc) + + # is that IRI even on a known HTTP scheme? + if collapse_http_schemes: + for scheme in base_scheme, cur_scheme: + if scheme not in ("http", "https"): + return None + else: + if not (base_scheme in ("http", "https") and base_scheme == cur_scheme): + return None + + # are the netlocs compatible? + if base_netloc != cur_netloc: + return None + + # are we below the application path? + base_path = base_path.rstrip("/") + if not cur_path.startswith(base_path): + return None + + return f"/{cur_path[len(base_path) :].lstrip('/')}" + + +class ClosingIterator: + """The WSGI specification requires that all middlewares and gateways + respect the `close` callback of the iterable returned by the application. + Because it is useful to add another close action to a returned iterable + and adding a custom iterable is a boring task this class can be used for + that:: + + return ClosingIterator(app(environ, start_response), [cleanup_session, + cleanup_locals]) + + If there is just one close function it can be passed instead of the list. + + A closing iterator is not needed if the application uses response objects + and finishes the processing if the response is started:: + + try: + return response(environ, start_response) + finally: + cleanup_session() + cleanup_locals() + """ + + def __init__( + self, + iterable: t.Iterable[bytes], + callbacks: t.Optional[ + t.Union[t.Callable[[], None], t.Iterable[t.Callable[[], None]]] + ] = None, + ) -> None: + iterator = iter(iterable) + self._next = t.cast(t.Callable[[], bytes], partial(next, iterator)) + if callbacks is None: + callbacks = [] + elif callable(callbacks): + callbacks = [callbacks] + else: + callbacks = list(callbacks) + iterable_close = getattr(iterable, "close", None) + if iterable_close: + callbacks.insert(0, iterable_close) + self._callbacks = callbacks + + def __iter__(self) -> "ClosingIterator": + return self + + def __next__(self) -> bytes: + return self._next() + + def close(self) -> None: + for callback in self._callbacks: + callback() + + +def wrap_file( + environ: "WSGIEnvironment", file: t.BinaryIO, buffer_size: int = 8192 +) -> t.Iterable[bytes]: + """Wraps a file. This uses the WSGI server's file wrapper if available + or otherwise the generic :class:`FileWrapper`. + + .. versionadded:: 0.5 + + If the file wrapper from the WSGI server is used it's important to not + iterate over it from inside the application but to pass it through + unchanged. If you want to pass out a file wrapper inside a response + object you have to set :attr:`Response.direct_passthrough` to `True`. + + More information about file wrappers are available in :pep:`333`. + + :param file: a :class:`file`-like object with a :meth:`~file.read` method. + :param buffer_size: number of bytes for one iteration. + """ + return environ.get("wsgi.file_wrapper", FileWrapper)( # type: ignore + file, buffer_size + ) + + +class FileWrapper: + """This class can be used to convert a :class:`file`-like object into + an iterable. It yields `buffer_size` blocks until the file is fully + read. + + You should not use this class directly but rather use the + :func:`wrap_file` function that uses the WSGI server's file wrapper + support if it's available. + + .. versionadded:: 0.5 + + If you're using this object together with a :class:`Response` you have + to use the `direct_passthrough` mode. + + :param file: a :class:`file`-like object with a :meth:`~file.read` method. + :param buffer_size: number of bytes for one iteration. + """ + + def __init__(self, file: t.BinaryIO, buffer_size: int = 8192) -> None: + self.file = file + self.buffer_size = buffer_size + + def close(self) -> None: + if hasattr(self.file, "close"): + self.file.close() + + def seekable(self) -> bool: + if hasattr(self.file, "seekable"): + return self.file.seekable() + if hasattr(self.file, "seek"): + return True + return False + + def seek(self, *args: t.Any) -> None: + if hasattr(self.file, "seek"): + self.file.seek(*args) + + def tell(self) -> t.Optional[int]: + if hasattr(self.file, "tell"): + return self.file.tell() + return None + + def __iter__(self) -> "FileWrapper": + return self + + def __next__(self) -> bytes: + data = self.file.read(self.buffer_size) + if data: + return data + raise StopIteration() + + +class _RangeWrapper: + # private for now, but should we make it public in the future ? + + """This class can be used to convert an iterable object into + an iterable that will only yield a piece of the underlying content. + It yields blocks until the underlying stream range is fully read. + The yielded blocks will have a size that can't exceed the original + iterator defined block size, but that can be smaller. + + If you're using this object together with a :class:`Response` you have + to use the `direct_passthrough` mode. + + :param iterable: an iterable object with a :meth:`__next__` method. + :param start_byte: byte from which read will start. + :param byte_range: how many bytes to read. + """ + + def __init__( + self, + iterable: t.Union[t.Iterable[bytes], t.BinaryIO], + start_byte: int = 0, + byte_range: t.Optional[int] = None, + ): + self.iterable = iter(iterable) + self.byte_range = byte_range + self.start_byte = start_byte + self.end_byte = None + + if byte_range is not None: + self.end_byte = start_byte + byte_range + + self.read_length = 0 + self.seekable = ( + hasattr(iterable, "seekable") and iterable.seekable() # type: ignore + ) + self.end_reached = False + + def __iter__(self) -> "_RangeWrapper": + return self + + def _next_chunk(self) -> bytes: + try: + chunk = next(self.iterable) + self.read_length += len(chunk) + return chunk + except StopIteration: + self.end_reached = True + raise + + def _first_iteration(self) -> t.Tuple[t.Optional[bytes], int]: + chunk = None + if self.seekable: + self.iterable.seek(self.start_byte) # type: ignore + self.read_length = self.iterable.tell() # type: ignore + contextual_read_length = self.read_length + else: + while self.read_length <= self.start_byte: + chunk = self._next_chunk() + if chunk is not None: + chunk = chunk[self.start_byte - self.read_length :] + contextual_read_length = self.start_byte + return chunk, contextual_read_length + + def _next(self) -> bytes: + if self.end_reached: + raise StopIteration() + chunk = None + contextual_read_length = self.read_length + if self.read_length == 0: + chunk, contextual_read_length = self._first_iteration() + if chunk is None: + chunk = self._next_chunk() + if self.end_byte is not None and self.read_length >= self.end_byte: + self.end_reached = True + return chunk[: self.end_byte - contextual_read_length] + return chunk + + def __next__(self) -> bytes: + chunk = self._next() + if chunk: + return chunk + self.end_reached = True + raise StopIteration() + + def close(self) -> None: + if hasattr(self.iterable, "close"): + self.iterable.close() # type: ignore + + +def _make_chunk_iter( + stream: t.Union[t.Iterable[bytes], t.BinaryIO], + limit: t.Optional[int], + buffer_size: int, +) -> t.Iterator[bytes]: + """Helper for the line and chunk iter functions.""" + if isinstance(stream, (bytes, bytearray, str)): + raise TypeError( + "Passed a string or byte object instead of true iterator or stream." + ) + if not hasattr(stream, "read"): + for item in stream: + if item: + yield item + return + stream = t.cast(t.BinaryIO, stream) + if not isinstance(stream, LimitedStream) and limit is not None: + stream = t.cast(t.BinaryIO, LimitedStream(stream, limit)) + _read = stream.read + while True: + item = _read(buffer_size) + if not item: + break + yield item + + +def make_line_iter( + stream: t.Union[t.Iterable[bytes], t.BinaryIO], + limit: t.Optional[int] = None, + buffer_size: int = 10 * 1024, + cap_at_buffer: bool = False, +) -> t.Iterator[bytes]: + """Safely iterates line-based over an input stream. If the input stream + is not a :class:`LimitedStream` the `limit` parameter is mandatory. + + This uses the stream's :meth:`~file.read` method internally as opposite + to the :meth:`~file.readline` method that is unsafe and can only be used + in violation of the WSGI specification. The same problem applies to the + `__iter__` function of the input stream which calls :meth:`~file.readline` + without arguments. + + If you need line-by-line processing it's strongly recommended to iterate + over the input stream using this helper function. + + .. versionchanged:: 0.8 + This function now ensures that the limit was reached. + + .. versionadded:: 0.9 + added support for iterators as input stream. + + .. versionadded:: 0.11.10 + added support for the `cap_at_buffer` parameter. + + :param stream: the stream or iterate to iterate over. + :param limit: the limit in bytes for the stream. (Usually + content length. Not necessary if the `stream` + is a :class:`LimitedStream`. + :param buffer_size: The optional buffer size. + :param cap_at_buffer: if this is set chunks are split if they are longer + than the buffer size. Internally this is implemented + that the buffer size might be exhausted by a factor + of two however. + """ + _iter = _make_chunk_iter(stream, limit, buffer_size) + + first_item = next(_iter, "") + if not first_item: + return + + s = _make_encode_wrapper(first_item) + empty = t.cast(bytes, s("")) + cr = t.cast(bytes, s("\r")) + lf = t.cast(bytes, s("\n")) + crlf = t.cast(bytes, s("\r\n")) + + _iter = t.cast(t.Iterator[bytes], chain((first_item,), _iter)) + + def _iter_basic_lines() -> t.Iterator[bytes]: + _join = empty.join + buffer: t.List[bytes] = [] + while True: + new_data = next(_iter, "") + if not new_data: + break + new_buf: t.List[bytes] = [] + buf_size = 0 + for item in t.cast( + t.Iterator[bytes], chain(buffer, new_data.splitlines(True)) + ): + new_buf.append(item) + buf_size += len(item) + if item and item[-1:] in crlf: + yield _join(new_buf) + new_buf = [] + elif cap_at_buffer and buf_size >= buffer_size: + rv = _join(new_buf) + while len(rv) >= buffer_size: + yield rv[:buffer_size] + rv = rv[buffer_size:] + new_buf = [rv] + buffer = new_buf + if buffer: + yield _join(buffer) + + # This hackery is necessary to merge 'foo\r' and '\n' into one item + # of 'foo\r\n' if we were unlucky and we hit a chunk boundary. + previous = empty + for item in _iter_basic_lines(): + if item == lf and previous[-1:] == cr: + previous += item + item = empty + if previous: + yield previous + previous = item + if previous: + yield previous + + +def make_chunk_iter( + stream: t.Union[t.Iterable[bytes], t.BinaryIO], + separator: bytes, + limit: t.Optional[int] = None, + buffer_size: int = 10 * 1024, + cap_at_buffer: bool = False, +) -> t.Iterator[bytes]: + """Works like :func:`make_line_iter` but accepts a separator + which divides chunks. If you want newline based processing + you should use :func:`make_line_iter` instead as it + supports arbitrary newline markers. + + .. versionadded:: 0.8 + + .. versionadded:: 0.9 + added support for iterators as input stream. + + .. versionadded:: 0.11.10 + added support for the `cap_at_buffer` parameter. + + :param stream: the stream or iterate to iterate over. + :param separator: the separator that divides chunks. + :param limit: the limit in bytes for the stream. (Usually + content length. Not necessary if the `stream` + is otherwise already limited). + :param buffer_size: The optional buffer size. + :param cap_at_buffer: if this is set chunks are split if they are longer + than the buffer size. Internally this is implemented + that the buffer size might be exhausted by a factor + of two however. + """ + _iter = _make_chunk_iter(stream, limit, buffer_size) + + first_item = next(_iter, b"") + if not first_item: + return + + _iter = t.cast(t.Iterator[bytes], chain((first_item,), _iter)) + if isinstance(first_item, str): + separator = _to_str(separator) + _split = re.compile(f"({re.escape(separator)})").split + _join = "".join + else: + separator = _to_bytes(separator) + _split = re.compile(b"(" + re.escape(separator) + b")").split + _join = b"".join + + buffer: t.List[bytes] = [] + while True: + new_data = next(_iter, b"") + if not new_data: + break + chunks = _split(new_data) + new_buf: t.List[bytes] = [] + buf_size = 0 + for item in chain(buffer, chunks): + if item == separator: + yield _join(new_buf) + new_buf = [] + buf_size = 0 + else: + buf_size += len(item) + new_buf.append(item) + + if cap_at_buffer and buf_size >= buffer_size: + rv = _join(new_buf) + while len(rv) >= buffer_size: + yield rv[:buffer_size] + rv = rv[buffer_size:] + new_buf = [rv] + buf_size = len(rv) + + buffer = new_buf + if buffer: + yield _join(buffer) + + +class LimitedStream(io.IOBase): + """Wraps a stream so that it doesn't read more than n bytes. If the + stream is exhausted and the caller tries to get more bytes from it + :func:`on_exhausted` is called which by default returns an empty + string. The return value of that function is forwarded + to the reader function. So if it returns an empty string + :meth:`read` will return an empty string as well. + + The limit however must never be higher than what the stream can + output. Otherwise :meth:`readlines` will try to read past the + limit. + + .. admonition:: Note on WSGI compliance + + calls to :meth:`readline` and :meth:`readlines` are not + WSGI compliant because it passes a size argument to the + readline methods. Unfortunately the WSGI PEP is not safely + implementable without a size argument to :meth:`readline` + because there is no EOF marker in the stream. As a result + of that the use of :meth:`readline` is discouraged. + + For the same reason iterating over the :class:`LimitedStream` + is not portable. It internally calls :meth:`readline`. + + We strongly suggest using :meth:`read` only or using the + :func:`make_line_iter` which safely iterates line-based + over a WSGI input stream. + + :param stream: the stream to wrap. + :param limit: the limit for the stream, must not be longer than + what the string can provide if the stream does not + end with `EOF` (like `wsgi.input`) + """ + + def __init__(self, stream: t.BinaryIO, limit: int) -> None: + self._read = stream.read + self._readline = stream.readline + self._pos = 0 + self.limit = limit + + def __iter__(self) -> "LimitedStream": + return self + + @property + def is_exhausted(self) -> bool: + """If the stream is exhausted this attribute is `True`.""" + return self._pos >= self.limit + + def on_exhausted(self) -> bytes: + """This is called when the stream tries to read past the limit. + The return value of this function is returned from the reading + function. + """ + # Read null bytes from the stream so that we get the + # correct end of stream marker. + return self._read(0) + + def on_disconnect(self) -> bytes: + """What should happen if a disconnect is detected? The return + value of this function is returned from read functions in case + the client went away. By default a + :exc:`~werkzeug.exceptions.ClientDisconnected` exception is raised. + """ + from .exceptions import ClientDisconnected + + raise ClientDisconnected() + + def exhaust(self, chunk_size: int = 1024 * 64) -> None: + """Exhaust the stream. This consumes all the data left until the + limit is reached. + + :param chunk_size: the size for a chunk. It will read the chunk + until the stream is exhausted and throw away + the results. + """ + to_read = self.limit - self._pos + chunk = chunk_size + while to_read > 0: + chunk = min(to_read, chunk) + self.read(chunk) + to_read -= chunk + + def read(self, size: t.Optional[int] = None) -> bytes: + """Read `size` bytes or if size is not provided everything is read. + + :param size: the number of bytes read. + """ + if self._pos >= self.limit: + return self.on_exhausted() + if size is None or size == -1: # -1 is for consistence with file + size = self.limit + to_read = min(self.limit - self._pos, size) + try: + read = self._read(to_read) + except (OSError, ValueError): + return self.on_disconnect() + if to_read and len(read) != to_read: + return self.on_disconnect() + self._pos += len(read) + return read + + def readline(self, size: t.Optional[int] = None) -> bytes: + """Reads one line from the stream.""" + if self._pos >= self.limit: + return self.on_exhausted() + if size is None: + size = self.limit - self._pos + else: + size = min(size, self.limit - self._pos) + try: + line = self._readline(size) + except (ValueError, OSError): + return self.on_disconnect() + if size and not line: + return self.on_disconnect() + self._pos += len(line) + return line + + def readlines(self, size: t.Optional[int] = None) -> t.List[bytes]: + """Reads a file into a list of strings. It calls :meth:`readline` + until the file is read to the end. It does support the optional + `size` argument if the underlying stream supports it for + `readline`. + """ + last_pos = self._pos + result = [] + if size is not None: + end = min(self.limit, last_pos + size) + else: + end = self.limit + while True: + if size is not None: + size -= last_pos - self._pos + if self._pos >= end: + break + result.append(self.readline(size)) + if size is not None: + last_pos = self._pos + return result + + def tell(self) -> int: + """Returns the position of the stream. + + .. versionadded:: 0.9 + """ + return self._pos + + def __next__(self) -> bytes: + line = self.readline() + if not line: + raise StopIteration() + return line + + def readable(self) -> bool: + return True diff --git a/BackEndChallenge/myproject/pyvenv.cfg b/BackEndChallenge/myproject/pyvenv.cfg new file mode 100644 index 000000000..f16b4a1fa --- /dev/null +++ b/BackEndChallenge/myproject/pyvenv.cfg @@ -0,0 +1,3 @@ +home = /usr/local/bin +include-system-site-packages = false +version = 3.8.5 diff --git a/BackEndChallenge/myproject/students.sqlite3 b/BackEndChallenge/myproject/students.sqlite3 new file mode 100644 index 0000000000000000000000000000000000000000..462d49d17a43dfd3e4026baf6ab213c55bec7442 GIT binary patch literal 12288 zcmeI#&r8EF6bJC66Lp2r-F7OxV+*1k{sYERP*$hLD)dx38llj2xTfHBf13yYC66|P zc->XT_d$NVgtX+7(|dVNYpv*gvt5^(uGy4v&TfbpV|-|%q1iC`iA{#lJ z{V>N9%oE=Cp##{500bZa0SG_<0uX=z1Rwwb2>eTd-H4yfe4p=fU9K9{>Aq5}?(FEq zG7(uqnYc|8va1xh7WVoS1HEIs^nD009U< x00Izz00bZa0SG_<0>>)gnv0?SKi22Py&wPq2tWV=5P$##AOHafKmY=JfnV0oLZScw literal 0 HcmV?d00001 diff --git a/BackEndChallenge/myproject/templates/index.html b/BackEndChallenge/myproject/templates/index.html new file mode 100644 index 000000000..2f0258e38 --- /dev/null +++ b/BackEndChallenge/myproject/templates/index.html @@ -0,0 +1,20 @@ + +

    Back end challenge!

    +

    {{embed}}

    +

    + +

    Enter Numbers:

    +

    +

    + +
    +

    Enter The Request you want to see:

    +

    +

    +
    +
    + +
    + \ No newline at end of file From 2621af5db8b9a6956f4a8b6c1298f34ee06afc4f Mon Sep 17 00:00:00 2001 From: Emily Miller <55591950+emm190@users.noreply.github.com> Date: Sun, 3 Oct 2021 17:45:19 -0400 Subject: [PATCH 19/29] Create README.md --- BackEndChallenge/README.md | 76 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 BackEndChallenge/README.md diff --git a/BackEndChallenge/README.md b/BackEndChallenge/README.md new file mode 100644 index 000000000..ba6d4a63a --- /dev/null +++ b/BackEndChallenge/README.md @@ -0,0 +1,76 @@ + + +
    +

    + + Logo + + +

    Headstorm Engineering Back End Challenge

    + +

    + I made a rest API using flask for the /data entry point. +

    +

    + + + + +
    +

    Table of Contents

    +
      +
    1. + +
    2. +
    3. + Run the project +
    4. +
    5. Improvements
    6. +
    7. Contact
    8. +
    +
    + + + + +### Built With + +* []() Python +* []() HTML + +### Using + +* []() Flask, python microframework +* []() Flask's SQLAlchemy & Flask's Restful API + + + +## Getting Started + +To get a local copy up and running follow these simple steps. + +### Prerequisites + +You will need to have python 3.8.5 & flask installed along with the flask dependencies that are listed at the top of the app.py file. Specifically install flask_sqlalchemy & flask_restful along with flask. + +### Installation + +1. Clone my repo :) +2. Install flask & flask requirements +3. I set up a virtual environment & installed flask & the dependencies that way + +### Run The Project +4. Type "python app.py" + +### Improvements +* I did not get a chance to add the requirements about the input being 500 numbers as json input. My current program takes in 5 numbers. It checks that there are exactly 5 integers, +* but does not check that there are 500 or the input. It only works if there are 5 numbers separated by commas. This addition to meet the 500 json requirements could be +* met with more time. + +### Contact +Please contact me with any issues running the code: +* email: emm190@pitt.edu +* phone: 330-987-0225 +* linkedIn: https://www.linkedin.com/in/emilymiller21/ From 7eaf71562708cfe51f90f510df7a142ee0449632 Mon Sep 17 00:00:00 2001 From: Emily Miller <55591950+emm190@users.noreply.github.com> Date: Sun, 3 Oct 2021 17:45:49 -0400 Subject: [PATCH 20/29] Update README.md --- BackEndChallenge/README.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/BackEndChallenge/README.md b/BackEndChallenge/README.md index ba6d4a63a..81e670477 100644 --- a/BackEndChallenge/README.md +++ b/BackEndChallenge/README.md @@ -2,9 +2,6 @@

    - - Logo -

    Headstorm Engineering Back End Challenge

    @@ -20,9 +17,7 @@

    Table of Contents

    1. -
    2. Run the project From ad925e7b8d2f5a609b4f15b99a28a6511321f6c2 Mon Sep 17 00:00:00 2001 From: Emily Miller <55591950+emm190@users.noreply.github.com> Date: Sun, 3 Oct 2021 17:46:13 -0400 Subject: [PATCH 21/29] Update README.md --- BackEndChallenge/README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/BackEndChallenge/README.md b/BackEndChallenge/README.md index 81e670477..554e626da 100644 --- a/BackEndChallenge/README.md +++ b/BackEndChallenge/README.md @@ -16,9 +16,7 @@

      Table of Contents

        -
      1. -
      2. Built With
      3. - +
      4. Built With
      5. Run the project
      6. From fe5089406ecf6724f6294f62436de1faac45fb59 Mon Sep 17 00:00:00 2001 From: Emily Miller <55591950+emm190@users.noreply.github.com> Date: Sun, 3 Oct 2021 17:46:34 -0400 Subject: [PATCH 22/29] Update README.md --- BackEndChallenge/README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/BackEndChallenge/README.md b/BackEndChallenge/README.md index 554e626da..6f9221f78 100644 --- a/BackEndChallenge/README.md +++ b/BackEndChallenge/README.md @@ -58,8 +58,7 @@ You will need to have python 3.8.5 & flask installed along with the flask depend 4. Type "python app.py" ### Improvements -* I did not get a chance to add the requirements about the input being 500 numbers as json input. My current program takes in 5 numbers. It checks that there are exactly 5 integers, -* but does not check that there are 500 or the input. It only works if there are 5 numbers separated by commas. This addition to meet the 500 json requirements could be +* I did not get a chance to add the requirements about the input being 500 numbers as json input. My current program takes in 5 numbers. It checks that there are exactly 5 integers, but does not check that there are 500 or the input. It only works if there are 5 numbers separated by commas. This addition to meet the 500 json requirements could be * met with more time. ### Contact From 8b07f055b63e1f7c5d1141527004417d262031f6 Mon Sep 17 00:00:00 2001 From: Emily Miller Date: Sun, 3 Oct 2021 18:03:59 -0400 Subject: [PATCH 23/29] added error checking --- BackEndChallenge/myproject/app.py | 11 +++++++++-- BackEndChallenge/myproject/bin/activate | 2 +- BackEndChallenge/myproject/bin/activate.csh | 2 +- BackEndChallenge/myproject/bin/activate.fish | 2 +- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/BackEndChallenge/myproject/app.py b/BackEndChallenge/myproject/app.py index 9eaebec4f..cda4074d8 100644 --- a/BackEndChallenge/myproject/app.py +++ b/BackEndChallenge/myproject/app.py @@ -45,10 +45,16 @@ def data(): idNumber = number1.id listOfNumbers = str(stringList) user = listOfNumbers.split(',') + for x in user: + try: + int(x) + except: + error_msg='There is something wrong with your formatting. Make sure you did not enter any letters. The only acceptable input is numbers separated by commas (with no space at the end)' + return render_template('index.html', embed=error_msg) intList = [int(x) for x in user] - print(len(intList)) + print(intList) if (len(intList)!=5): - error_msg='You have to enter 500 numbers. You did not. Try again' + error_msg='You have to enter 5 numbers. You did not. Try again' return render_template('index.html', embed=error_msg) print(idNumber) return redirect(url_for('success',listOfNumbers = Number.query.filter_by(id=idNumber).all())) @@ -67,6 +73,7 @@ def getValue(): def success(listOfNumbers): listOfNumbers = str(listOfNumbers) user = listOfNumbers.split(',') + print(user) intList = [int(x) for x in user] intList.sort() print(intList) diff --git a/BackEndChallenge/myproject/bin/activate b/BackEndChallenge/myproject/bin/activate index 4edfb4e06..6e615a2f6 100644 --- a/BackEndChallenge/myproject/bin/activate +++ b/BackEndChallenge/myproject/bin/activate @@ -37,7 +37,7 @@ deactivate () { # unset irrelevant variables deactivate nondestructive -VIRTUAL_ENV="/Users/emilymariemiller/Desktop/BackEndChallenge/myproject" +VIRTUAL_ENV="/Users/emilymariemiller/Desktop/FrontEndChallenge/BackEndChallenge/myproject" export VIRTUAL_ENV _OLD_VIRTUAL_PATH="$PATH" diff --git a/BackEndChallenge/myproject/bin/activate.csh b/BackEndChallenge/myproject/bin/activate.csh index 03acf1fd3..65a0cbc18 100644 --- a/BackEndChallenge/myproject/bin/activate.csh +++ b/BackEndChallenge/myproject/bin/activate.csh @@ -8,7 +8,7 @@ alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PA # Unset irrelevant variables. deactivate nondestructive -setenv VIRTUAL_ENV "/Users/emilymariemiller/Desktop/BackEndChallenge/myproject" +setenv VIRTUAL_ENV "/Users/emilymariemiller/Desktop/FrontEndChallenge/BackEndChallenge/myproject" set _OLD_VIRTUAL_PATH="$PATH" setenv PATH "$VIRTUAL_ENV/bin:$PATH" diff --git a/BackEndChallenge/myproject/bin/activate.fish b/BackEndChallenge/myproject/bin/activate.fish index 8b5a0dc4d..bf30a8abb 100644 --- a/BackEndChallenge/myproject/bin/activate.fish +++ b/BackEndChallenge/myproject/bin/activate.fish @@ -29,7 +29,7 @@ end # unset irrelevant variables deactivate nondestructive -set -gx VIRTUAL_ENV "/Users/emilymariemiller/Desktop/BackEndChallenge/myproject" +set -gx VIRTUAL_ENV "/Users/emilymariemiller/Desktop/FrontEndChallenge/BackEndChallenge/myproject" set -gx _OLD_VIRTUAL_PATH $PATH set -gx PATH "$VIRTUAL_ENV/bin" $PATH From d1130587834c3e8b0966bbdd376693cde639af28 Mon Sep 17 00:00:00 2001 From: Emily Miller Date: Sun, 3 Oct 2021 18:04:47 -0400 Subject: [PATCH 24/29] Update README.md --- BackEndChallenge/README.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/BackEndChallenge/README.md b/BackEndChallenge/README.md index 6f9221f78..dd7f07876 100644 --- a/BackEndChallenge/README.md +++ b/BackEndChallenge/README.md @@ -6,7 +6,7 @@

        Headstorm Engineering Back End Challenge

        - I made a rest API using flask for the /data entry point. + I made a rest API using flask for the /data entry point.

        @@ -50,19 +50,20 @@ You will need to have python 3.8.5 & flask installed along with the flask depend ### Installation -1. Clone my repo :) -2. Install flask & flask requirements -3. I set up a virtual environment & installed flask & the dependencies that way +1. Clone my repo :) +2. Install flask & flask requirements +3. I set up a virtual environment & installed flask & the dependencies that way +4. To run my virtual environment, type "source bin/activate" from the myproject directory ### Run The Project 4. Type "python app.py" ### Improvements -* I did not get a chance to add the requirements about the input being 500 numbers as json input. My current program takes in 5 numbers. It checks that there are exactly 5 integers, but does not check that there are 500 or the input. It only works if there are 5 numbers separated by commas. This addition to meet the 500 json requirements could be +* I did not get a chance to add the requirements about the input being 500 numbers as json input. My current program takes in 5 numbers. It checks that there are exactly 5 integers, but does not check that there are 500 or the input. It only works if there are 5 numbers separated by commas. This addition to meet the 500 json requirements could be * met with more time. -### Contact -Please contact me with any issues running the code: +### Contact +Please contact me with any issues running the code: * email: emm190@pitt.edu -* phone: 330-987-0225 +* phone: 330-987-0225 * linkedIn: https://www.linkedin.com/in/emilymiller21/ From 4b147f933cf648255f2b70f1a3a21c8ca45acbab Mon Sep 17 00:00:00 2001 From: Emily Miller Date: Sun, 3 Oct 2021 20:28:06 -0400 Subject: [PATCH 25/29] added db challenge --- DatabaseChallenge/Entity Relation.drawio.pdf | Bin 0 -> 41040 bytes DatabaseChallenge/dbchallenge.py | 16 ++++++++++++++++ DatabaseChallenge/oldDb.json | 11 +++++++++++ 3 files changed, 27 insertions(+) create mode 100644 DatabaseChallenge/Entity Relation.drawio.pdf create mode 100644 DatabaseChallenge/dbchallenge.py create mode 100644 DatabaseChallenge/oldDb.json diff --git a/DatabaseChallenge/Entity Relation.drawio.pdf b/DatabaseChallenge/Entity Relation.drawio.pdf new file mode 100644 index 0000000000000000000000000000000000000000..5238495736412bc198038089f9cd25ee111fd268 GIT binary patch literal 41040 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 literal 0 HcmV?d00001 diff --git a/DatabaseChallenge/dbchallenge.py b/DatabaseChallenge/dbchallenge.py new file mode 100644 index 000000000..d4b2a8e50 --- /dev/null +++ b/DatabaseChallenge/dbchallenge.py @@ -0,0 +1,16 @@ +import json + +with open('oldDb.json') as f: + data = json.load(f) + +print(data["record_id"]) +print(data["name"]) +print(data["cell_phone"]) +print(data["work_phone"]) +print(data["email"]) +print(data["address"]) +print(data["basic_widget"]) +print(data["advanced_widget"]) +print(data["protection_plan"]) + +f.close() diff --git a/DatabaseChallenge/oldDb.json b/DatabaseChallenge/oldDb.json new file mode 100644 index 000000000..4ea58491d --- /dev/null +++ b/DatabaseChallenge/oldDb.json @@ -0,0 +1,11 @@ +{ + "record_id": "1234", + "name": "Joe Smith", + "cell_phone": "405.867.5309", + "work_phone": "123.123.1234", + "email": "joe_s@gmail.com", + "address": "123 Vic Way, Dallas TX 75001", + "basic_widget": "37", + "advanced_widget": "12", + "protection_plan": "True" +} From edc94cd3663bed6a618b785714c6aed78e339c02 Mon Sep 17 00:00:00 2001 From: Emily Miller Date: Sun, 3 Oct 2021 20:32:12 -0400 Subject: [PATCH 26/29] added var to py --- DatabaseChallenge/dbchallenge.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/DatabaseChallenge/dbchallenge.py b/DatabaseChallenge/dbchallenge.py index d4b2a8e50..247a60326 100644 --- a/DatabaseChallenge/dbchallenge.py +++ b/DatabaseChallenge/dbchallenge.py @@ -13,4 +13,13 @@ print(data["advanced_widget"]) print(data["protection_plan"]) +record_id = int(data["record_id"]) +name = str(data["name"]) +cell_phone = str(data["cell_phone"]) +work_phone = str(data["work_phone"]) +email = str(data["email"]) +basic_widget = str(data["basic_widget"]) +advanced_widget = str(data["advanced_widget"]) +protection_plane = data["protection_plan"]) + f.close() From 8578b0253b6f39821446fedfc778dbda60ef1c0f Mon Sep 17 00:00:00 2001 From: Emily Miller Date: Sun, 3 Oct 2021 20:52:40 -0400 Subject: [PATCH 27/29] Update dbchallenge.py --- DatabaseChallenge/dbchallenge.py | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/DatabaseChallenge/dbchallenge.py b/DatabaseChallenge/dbchallenge.py index 247a60326..2ea6cc886 100644 --- a/DatabaseChallenge/dbchallenge.py +++ b/DatabaseChallenge/dbchallenge.py @@ -3,23 +3,31 @@ with open('oldDb.json') as f: data = json.load(f) -print(data["record_id"]) -print(data["name"]) -print(data["cell_phone"]) -print(data["work_phone"]) -print(data["email"]) -print(data["address"]) -print(data["basic_widget"]) -print(data["advanced_widget"]) -print(data["protection_plan"]) - record_id = int(data["record_id"]) name = str(data["name"]) cell_phone = str(data["cell_phone"]) work_phone = str(data["work_phone"]) email = str(data["email"]) +address = str(data["address"]) basic_widget = str(data["basic_widget"]) advanced_widget = str(data["advanced_widget"]) -protection_plane = data["protection_plan"]) +protection_plan = data["protection_plan"] +print("Since the readme just mentioned printing insert statements, assume the PK is set at autoincrement :)") +print("and assume that the tables are created & filled in with the proper information as shown in the entity diagram") + +print("SQL statements for new SQL db") +print("CREATE DATABASE widgetShop;") +print("CREATE TABLE Customer (...)") +print("CREATE TABLE Records (...)") +print("CREATE TABLE Order_Details (...)") + +print("INSERT INTO Customer (customer_name, customer_cell_phone, customer_work_phone, customer_email, customer_address)") +print(('VALUES (\'{}\', \'{}\', \'{}\', \'{}\', \'{}\')').format(name, cell_phone, work_phone, email, address)) + +print("INSERT INTO Records (customer_id, order_date)") +print('VALUES (\'1\')') + +print("INSERT INTO Order_Details (record_id, protection_plan, widget_type)") +print(('VALUES (\'{}\', \'{}\', \'{}\')').format(record_id, protection_plan, basic_widget)) f.close() From cfcaa44ac8ce39baa7e220fc32a6dbe4142d5150 Mon Sep 17 00:00:00 2001 From: Emily Miller <55591950+emm190@users.noreply.github.com> Date: Sun, 3 Oct 2021 20:54:13 -0400 Subject: [PATCH 28/29] Create README.md --- DatabaseChallenge/README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 DatabaseChallenge/README.md diff --git a/DatabaseChallenge/README.md b/DatabaseChallenge/README.md new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/DatabaseChallenge/README.md @@ -0,0 +1 @@ + From 7179da8f03f1b7efb7180bfd311499fef1010fb3 Mon Sep 17 00:00:00 2001 From: Emily Miller <55591950+emm190@users.noreply.github.com> Date: Sun, 3 Oct 2021 20:56:39 -0400 Subject: [PATCH 29/29] Update README.md --- DatabaseChallenge/README.md | 53 +++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/DatabaseChallenge/README.md b/DatabaseChallenge/README.md index 8b1378917..4e952e419 100644 --- a/DatabaseChallenge/README.md +++ b/DatabaseChallenge/README.md @@ -1 +1,54 @@ + +
        +

        +

        Headstorm Engineering Database Challenge

        + +

        + I made an entity diagram showing the new relational database & a mock python script to convert it to a SQL database. +

        +

        + + + + +
        +

        Table of Contents

        +
          +
        1. Built With
        2. +
        3. + Run the project +
        4. +
        5. Contact
        6. +
        +
        + + + + +### Built With + +* []() Python +* []() json + + +## Getting Started + +To get a local copy up and running follow these simple steps. + +### Prerequisites + +You will need to have python installed :) + +### Installation + +1. Clone my repo :) + +### Run The Project +2. Type "python dbchallenge.py" + +### Contact +Please contact me with any issues running the code: +* email: emm190@pitt.edu +* phone: 330-987-0225 +* linkedIn: https://www.linkedin.com/in/emilymiller21/