Home | History | Annotate | Download | only in http
      1 /*
      2  * Copyright (C) 2007 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.internal.http;
     18 
     19 import android.text.format.Time;
     20 
     21 import java.util.Calendar;
     22 import java.util.regex.Matcher;
     23 import java.util.regex.Pattern;
     24 
     25 /**
     26  * Helper for parsing an HTTP date.
     27  */
     28 public final class HttpDateTime {
     29 
     30     /*
     31      * Regular expression for parsing HTTP-date.
     32      *
     33      * Wdy, DD Mon YYYY HH:MM:SS GMT
     34      * RFC 822, updated by RFC 1123
     35      *
     36      * Weekday, DD-Mon-YY HH:MM:SS GMT
     37      * RFC 850, obsoleted by RFC 1036
     38      *
     39      * Wdy Mon DD HH:MM:SS YYYY
     40      * ANSI C's asctime() format
     41      *
     42      * with following variations
     43      *
     44      * Wdy, DD-Mon-YYYY HH:MM:SS GMT
     45      * Wdy, (SP)D Mon YYYY HH:MM:SS GMT
     46      * Wdy,DD Mon YYYY HH:MM:SS GMT
     47      * Wdy, DD-Mon-YY HH:MM:SS GMT
     48      * Wdy, DD Mon YYYY HH:MM:SS -HHMM
     49      * Wdy, DD Mon YYYY HH:MM:SS
     50      * Wdy Mon (SP)D HH:MM:SS YYYY
     51      * Wdy Mon DD HH:MM:SS YYYY GMT
     52      *
     53      * HH can be H if the first digit is zero.
     54      *
     55      * Mon can be the full name of the month.
     56      */
     57     private static final String HTTP_DATE_RFC_REGEXP =
     58             "([0-9]{1,2})[- ]([A-Za-z]{3,9})[- ]([0-9]{2,4})[ ]"
     59             + "([0-9]{1,2}:[0-9][0-9]:[0-9][0-9])";
     60 
     61     private static final String HTTP_DATE_ANSIC_REGEXP =
     62             "[ ]([A-Za-z]{3,9})[ ]+([0-9]{1,2})[ ]"
     63             + "([0-9]{1,2}:[0-9][0-9]:[0-9][0-9])[ ]([0-9]{2,4})";
     64 
     65     /**
     66      * The compiled version of the HTTP-date regular expressions.
     67      */
     68     private static final Pattern HTTP_DATE_RFC_PATTERN =
     69             Pattern.compile(HTTP_DATE_RFC_REGEXP);
     70     private static final Pattern HTTP_DATE_ANSIC_PATTERN =
     71             Pattern.compile(HTTP_DATE_ANSIC_REGEXP);
     72 
     73     private static class TimeOfDay {
     74         TimeOfDay(int h, int m, int s) {
     75             this.hour = h;
     76             this.minute = m;
     77             this.second = s;
     78         }
     79 
     80         int hour;
     81         int minute;
     82         int second;
     83     }
     84 
     85     public static long parse(String timeString)
     86             throws IllegalArgumentException {
     87 
     88         int date = 1;
     89         int month = Calendar.JANUARY;
     90         int year = 1970;
     91         TimeOfDay timeOfDay;
     92 
     93         Matcher rfcMatcher = HTTP_DATE_RFC_PATTERN.matcher(timeString);
     94         if (rfcMatcher.find()) {
     95             date = getDate(rfcMatcher.group(1));
     96             month = getMonth(rfcMatcher.group(2));
     97             year = getYear(rfcMatcher.group(3));
     98             timeOfDay = getTime(rfcMatcher.group(4));
     99         } else {
    100             Matcher ansicMatcher = HTTP_DATE_ANSIC_PATTERN.matcher(timeString);
    101             if (ansicMatcher.find()) {
    102                 month = getMonth(ansicMatcher.group(1));
    103                 date = getDate(ansicMatcher.group(2));
    104                 timeOfDay = getTime(ansicMatcher.group(3));
    105                 year = getYear(ansicMatcher.group(4));
    106             } else {
    107                 throw new IllegalArgumentException();
    108             }
    109         }
    110 
    111         // FIXME: Y2038 BUG!
    112         if (year >= 2038) {
    113             year = 2038;
    114             month = Calendar.JANUARY;
    115             date = 1;
    116         }
    117 
    118         Time time = new Time(Time.TIMEZONE_UTC);
    119         time.set(timeOfDay.second, timeOfDay.minute, timeOfDay.hour, date,
    120                 month, year);
    121         return time.toMillis(false /* use isDst */);
    122     }
    123 
    124     private static int getDate(String dateString) {
    125         if (dateString.length() == 2) {
    126             return (dateString.charAt(0) - '0') * 10
    127                     + (dateString.charAt(1) - '0');
    128         } else {
    129             return (dateString.charAt(0) - '0');
    130         }
    131     }
    132 
    133     /*
    134      * jan = 9 + 0 + 13 = 22
    135      * feb = 5 + 4 + 1 = 10
    136      * mar = 12 + 0 + 17 = 29
    137      * apr = 0 + 15 + 17 = 32
    138      * may = 12 + 0 + 24 = 36
    139      * jun = 9 + 20 + 13 = 42
    140      * jul = 9 + 20 + 11 = 40
    141      * aug = 0 + 20 + 6 = 26
    142      * sep = 18 + 4 + 15 = 37
    143      * oct = 14 + 2 + 19 = 35
    144      * nov = 13 + 14 + 21 = 48
    145      * dec = 3 + 4 + 2 = 9
    146      */
    147     private static int getMonth(String monthString) {
    148         int hash = Character.toLowerCase(monthString.charAt(0)) +
    149                 Character.toLowerCase(monthString.charAt(1)) +
    150                 Character.toLowerCase(monthString.charAt(2)) - 3 * 'a';
    151         switch (hash) {
    152             case 22:
    153                 return Calendar.JANUARY;
    154             case 10:
    155                 return Calendar.FEBRUARY;
    156             case 29:
    157                 return Calendar.MARCH;
    158             case 32:
    159                 return Calendar.APRIL;
    160             case 36:
    161                 return Calendar.MAY;
    162             case 42:
    163                 return Calendar.JUNE;
    164             case 40:
    165                 return Calendar.JULY;
    166             case 26:
    167                 return Calendar.AUGUST;
    168             case 37:
    169                 return Calendar.SEPTEMBER;
    170             case 35:
    171                 return Calendar.OCTOBER;
    172             case 48:
    173                 return Calendar.NOVEMBER;
    174             case 9:
    175                 return Calendar.DECEMBER;
    176             default:
    177                 throw new IllegalArgumentException();
    178         }
    179     }
    180 
    181     private static int getYear(String yearString) {
    182         if (yearString.length() == 2) {
    183             int year = (yearString.charAt(0) - '0') * 10
    184                     + (yearString.charAt(1) - '0');
    185             if (year >= 70) {
    186                 return year + 1900;
    187             } else {
    188                 return year + 2000;
    189             }
    190         } else if (yearString.length() == 3) {
    191             // According to RFC 2822, three digit years should be added to 1900.
    192             int year = (yearString.charAt(0) - '0') * 100
    193                     + (yearString.charAt(1) - '0') * 10
    194                     + (yearString.charAt(2) - '0');
    195             return year + 1900;
    196         } else if (yearString.length() == 4) {
    197              return (yearString.charAt(0) - '0') * 1000
    198                     + (yearString.charAt(1) - '0') * 100
    199                     + (yearString.charAt(2) - '0') * 10
    200                     + (yearString.charAt(3) - '0');
    201         } else {
    202              return 1970;
    203         }
    204     }
    205 
    206     private static TimeOfDay getTime(String timeString) {
    207         // HH might be H
    208         int i = 0;
    209         int hour = timeString.charAt(i++) - '0';
    210         if (timeString.charAt(i) != ':')
    211             hour = hour * 10 + (timeString.charAt(i++) - '0');
    212         // Skip ':'
    213         i++;
    214 
    215         int minute = (timeString.charAt(i++) - '0') * 10
    216                     + (timeString.charAt(i++) - '0');
    217         // Skip ':'
    218         i++;
    219 
    220         int second = (timeString.charAt(i++) - '0') * 10
    221                   + (timeString.charAt(i++) - '0');
    222 
    223         return new TimeOfDay(hour, minute, second);
    224     }
    225 }
    226