jenkins-bot has submitted this change and it was merged.

Change subject: Validate input dates prior to processing
......................................................................


Validate input dates prior to processing

Adds a function to check that dates are valid and in the correct format
before further processing and uses it where applicable.

Bug: T139315
Change-Id: Iaf6d5feac1c1d6b7f8ebc3032af6acd7bc491efe
---
M lib/dateUtil.js
M lib/feed/featured-image.js
M lib/feed/featured.js
M lib/feed/most-read.js
M spec.yaml
M test/lib/dateUtil/date-util-test.js
6 files changed, 125 insertions(+), 50 deletions(-)

Approvals:
  BearND: Looks good to me, approved
  jenkins-bot: Verified



diff --git a/lib/dateUtil.js b/lib/dateUtil.js
index f74b042..ea4f1a9 100644
--- a/lib/dateUtil.js
+++ b/lib/dateUtil.js
@@ -1,6 +1,7 @@
 'use strict';
 
 var sUtil = require('../lib/util');
+var HTTPError = sUtil.HTTPError;
 
 var ONE_DAY = 86400000;
 
@@ -40,6 +41,10 @@
     return req.params.yyyy + req.params.mm + req.params.dd;
 }
 
+function hyphenDelimitedDateString(req) {
+    return req.params.yyyy + '-' + req.params.mm + '-' + req.params.dd;
+}
+
 function iso8601DateFrom(req) {
     return req.params.yyyy + '-' + req.params.mm + '-' + req.params.dd + 'Z';
 }
@@ -66,12 +71,50 @@
     '-' + pad(date.getUTCDate());
 }
 
+//Validate the input date by checking whether UTC-format constructed from the
+//input components matches the values actually provided.
+function isValidDate(dateString, year, month, day) {
+    return new Date(Date.UTC(year, month-1, 
day)).toISOString().split('T').shift() === dateString;
+}
+
+//Check that @param year is in the interval (2001 <= year <= (current year + 
1))
+function isWithinBounds(year) {
+    return year >= 2001 && year <= new Date().getUTCFullYear() + 1;
+}
+
+function throwDateError() {
+    throw new HTTPError({
+        status: 404,
+        type: 'not_found',
+        title: 'Not found.',
+        detail: 'Invalid date provided.  Please request a valid date of format 
yyyy\/mm\/dd'
+    });
+}
+
+// Validates that the input string is a valid date in the expected format
+function validate(dateString) {
+    var parts = dateString.split("-");
+    var year = parseInt(parts[0], 10);
+    var month = parseInt(parts[1], 10);
+    var day = parseInt(parts[2], 10);
+
+    try {
+        if (!(isValidDate(dateString, year, month, day) && 
isWithinBounds(year))) {
+            throwDateError();
+        }
+    } catch (error) {
+        throwDateError();
+    }
+}
+
 module.exports = {
     formatDateEnglish: formatDateEnglish,
     getRequestedDate: getRequestedDate,
     dateStringFrom: dateStringFrom,
+    hyphenDelimitedDateString: hyphenDelimitedDateString,
     iso8601DateFrom: iso8601DateFrom,
     formatISODate: formatISODate,
+    validate: validate,
     pad: pad,
     ONE_DAY: ONE_DAY
 };
diff --git a/lib/feed/featured-image.js b/lib/feed/featured-image.js
index a0121e4..fc46578 100644
--- a/lib/feed/featured-image.js
+++ b/lib/feed/featured-image.js
@@ -95,6 +95,8 @@
 }
 
 function promise(app, req, dontThrow) {
+    var date = dateUtil.hyphenDelimitedDateString(req);
+    dateUtil.validate(date);
     return requestPictureOfTheDay(app, req.params.domain, 
dateUtil.getRequestedDate(req), dontThrow)
     .then(function (response) {
         mwapi.checkForQueryPagesInResponse(req, response);
diff --git a/lib/feed/featured.js b/lib/feed/featured.js
index b4b9e8c..2be2b3c 100644
--- a/lib/feed/featured.js
+++ b/lib/feed/featured.js
@@ -71,6 +71,8 @@
 }
 
 function promise(app, req, dontThrow) {
+    var date = dateUtil.hyphenDelimitedDateString(req);
+    dateUtil.validate(date);
     if (req.params.domain.indexOf('en') !== 0) {
         if (dontThrow) {
             return {};
diff --git a/lib/feed/most-read.js b/lib/feed/most-read.js
index 5a9bf44..a23e513 100644
--- a/lib/feed/most-read.js
+++ b/lib/feed/most-read.js
@@ -47,7 +47,9 @@
 
 function promise(app, req, aggregated) {
     var goodTitles;
-    return getTopPageviews(app, req, aggregated).then(function (response) {
+    var date = dateUtil.hyphenDelimitedDateString(req);
+    dateUtil.validate(date);
+    return getTopPageviews(app, req).then(function (response) {
         var queryTitlesList, rankedTitles = response.body
                          && response.body.items
                          && response.body.items[0]
diff --git a/spec.yaml b/spec.yaml
index f9b85aa..a0b8442 100644
--- a/spec.yaml
+++ b/spec.yaml
@@ -67,31 +67,31 @@
         - name: yyyy
           in: path
           description: "Year the aggregated content is requested for"
-          type: integer
+          type: string
           required: true
-          minimum: 2016
-          maximum: 2999
+          minimum: "2016"
+          maximum: "2999"
         - name: mm
           in: path
           description: "Month the aggregated content is requested for"
-          type: integer
+          type: string
           required: true
-          minimum: 01
-          maximum: 12
+          minimum: "01"
+          maximum: "12"
         - name: dd
           in: path
           description: "Day of the month the aggregated content is requested 
for"
-          type: integer
+          type: string
           required: true
-          minimum: 01
-          maximum: 31
+          minimum: "01"
+          maximum: "31"
       x-amples:
         - title: retrieve aggregated feed content for April 29, 2016
           request:
             params:
-              yyyy: 2016
-              mm: 04
-              dd: 29
+              yyyy: "2016"
+              mm: "04"
+              dd: "29"
           response:
             status: 200
             headers:
@@ -147,31 +147,31 @@
         - name: yyyy
           in: path
           description: "Year the featured article is requested for"
-          type: integer
+          type: string
           required: true
-          minimum: 2016
-          maximum: 2999
+          minimum: "2016"
+          maximum: "2999"
         - name: mm
           in: path
           description: "Month the featured article is requested for"
-          type: integer
+          type: string
           required: true
-          minimum: 1
-          maximum: 12
+          minimum: "01"
+          maximum: "12"
         - name: dd
           in: path
           description: "Day of the month the featured article is requested for"
-          type: integer
+          type: string
           required: true
-          minimum: 1
-          maximum: 31
+          minimum: "01"
+          maximum: "31"
       x-amples:
         - title: retrieve title of the featured article for April 29, 2016
           request:
             params:
-              yyyy: 2016
-              mm: 4
-              dd: 29
+              yyyy: "2016"
+              mm: "04"
+              dd: "29"
           response:
             status: 200
             headers:
@@ -196,31 +196,31 @@
         - name: yyyy
           in: path
           description: "Year the featured image is requested for"
-          type: integer
+          type: string
           required: true
-          minimum: 2016
-          maximum: 2999
+          minimum: "2016"
+          maximum: "2999"
         - name: mm
           in: path
           description: "Month the featured image is requested for"
-          type: integer
+          type: string
           required: true
-          minimum: 1
-          maximum: 12
+          minimum: "01"
+          maximum: "12"
         - name: dd
           in: path
           description: "Day of the month the featured image is requested for"
-          type: integer
+          type: string
           required: true
-          minimum: 1
-          maximum: 31
+          minimum: "01"
+          maximum: "31"
       x-amples:
         - title: retrieve featured image data for April 29, 2016
           request:
             params:
-              yyyy: 2016
-              mm: 4
-              dd: 29
+              yyyy: "2016"
+              mm: "04"
+              dd: "29"
           response:
             status: 200
             headers:
@@ -251,31 +251,31 @@
         - name: yyyy
           in: path
           description: "Year the most-read articles are requested for"
-          type: integer
+          type: string
           required: true
-          minimum: 2016
-          maximum: 2999
+          minimum: "2016"
+          maximum: "2999"
         - name: mm
           in: path
           description: "Month the most-read articles are requested for"
-          type: integer
+          type: string
           required: true
-          minimum: 01
-          maximum: 12
+          minimum: "01"
+          maximum: "12"
         - name: dd
           in: path
           description: "Day of the month the most-read articles are requested 
for"
-          type: integer
+          type: string
           required: true
-          minimum: 01
-          maximum: 31
+          minimum: "01"
+          maximum: "31"
       x-amples:
         - title: retrieve the most-read articles for January 1, 2016
           request:
             params:
-              yyyy: '2016'
-              mm: '01'
-              dd: '01'
+              yyyy: "2016"
+              mm: "01"
+              dd: "01"
           response:
             status: 200
             headers:
diff --git a/test/lib/dateUtil/date-util-test.js 
b/test/lib/dateUtil/date-util-test.js
index 85c6adb..bcc1742 100644
--- a/test/lib/dateUtil/date-util-test.js
+++ b/test/lib/dateUtil/date-util-test.js
@@ -2,11 +2,12 @@
 
 var assert = require('../../utils/assert.js');
 var dateUtil = require('../../../lib/dateUtil');
+var HTTPError = require('../../../lib/util').HTTPError;
 
 describe('lib:dateUtil', function() {
     this.timeout(20000);
 
-    it('getRequestedDate(2016/04/15) should return a valid Date object', 
function() {
+    it('getRequestedDate(2016-04-15) should return a valid Date object', 
function() {
         var actual = dateUtil.getRequestedDate({
             params: {
                 yyyy: 2016,
@@ -18,4 +19,29 @@
         assert.equal(actual.getUTCMonth(), 4 - 1);
         assert.equal(actual.getUTCDate(), 15);
     });
+
+    it('date format validation should reject invalid formats', function() {
+        assert.throws(function() { dateUtil.validate('2016-7-4'); }, 
HTTPError);
+        assert.throws(function() { dateUtil.validate('2016-07-4'); }, 
HTTPError);
+        assert.throws(function() { dateUtil.validate('2016-7-04'); }, 
HTTPError);
+        assert.throws(function() { dateUtil.validate('00000002016-07-04'); }, 
HTTPError);
+        assert.throws(function() { dateUtil.validate('2016-000000007-04'); }, 
HTTPError);
+        assert.throws(function() { dateUtil.validate('2016-07-000000004'); }, 
HTTPError);
+        assert.throws(function() { dateUtil.validate('2039-07-04'); }, 
HTTPError);
+        assert.throws(function() { dateUtil.validate('2016-13-04'); }, 
HTTPError);
+        assert.throws(function() { dateUtil.validate('2016-07-34'); }, 
HTTPError);
+        assert.throws(function() { dateUtil.validate('0000-07-04'); }, 
HTTPError);
+        assert.throws(function() { dateUtil.validate('2016-00-04'); }, 
HTTPError);
+        assert.throws(function() { dateUtil.validate('2016-07-00'); }, 
HTTPError);
+        assert.throws(function() { dateUtil.validate('abcd-07-04'); }, 
HTTPError);
+        assert.throws(function() { dateUtil.validate('2016-ef-04'); }, 
HTTPError);
+        assert.throws(function() { dateUtil.validate('2016-07-gh'); }, 
HTTPError);
+        assert.throws(function() { dateUtil.validate('*!@#-07-04'); }, 
HTTPError);
+        assert.throws(function() { dateUtil.validate('2016-*!-04'); }, 
HTTPError);
+        assert.throws(function() { dateUtil.validate('2016-07-@#'); }, 
HTTPError);
+        assert.throws(function() { dateUtil.validate('\u00002016-07-04'); }, 
HTTPError);
+        assert.throws(function() { dateUtil.validate('2016-\u000007-04'); }, 
HTTPError);
+        assert.throws(function() { dateUtil.validate('2016-07-\u000004'); }, 
HTTPError);
+        assert.doesNotThrow(function() { dateUtil.validate('2016-07-04'); });
+    })
 });

-- 
To view, visit https://gerrit.wikimedia.org/r/297344
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: Iaf6d5feac1c1d6b7f8ebc3032af6acd7bc491efe
Gerrit-PatchSet: 5
Gerrit-Project: mediawiki/services/mobileapps
Gerrit-Branch: master
Gerrit-Owner: Mholloway <[email protected]>
Gerrit-Reviewer: BearND <[email protected]>
Gerrit-Reviewer: Dbrant <[email protected]>
Gerrit-Reviewer: Fjalapeno <[email protected]>
Gerrit-Reviewer: GWicke <[email protected]>
Gerrit-Reviewer: Jhernandez <[email protected]>
Gerrit-Reviewer: Mholloway <[email protected]>
Gerrit-Reviewer: Mhurd <[email protected]>
Gerrit-Reviewer: Mobrovac <[email protected]>
Gerrit-Reviewer: Niedzielski <[email protected]>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to