File Source: WeblogPageRequest.java

     1  /*
     2   * Licensed to the Apache Software Foundation (ASF) under one or more
     3   *  contributor license agreements.  The ASF licenses this file to You
     4   * under the Apache License, Version 2.0 (the "License"); you may not
     5   * 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.  For additional information regarding
    15   * copyright in this work, please see the NOTICE file in the top level
    16   * directory of this distribution.
    17   */
    18  
    19  package org.apache.roller.weblogger.ui.rendering.util;
    20  
    21  import java.util.HashMap;
    22  import java.util.List;
    23  import java.util.Map;
    24  import javax.servlet.http.HttpServletRequest;
    25  import org.apache.commons.lang.StringUtils;
    26  import org.apache.commons.logging.Log;
    27  import org.apache.commons.logging.LogFactory;
    28  import org.apache.roller.weblogger.WebloggerException;
    29  import org.apache.roller.weblogger.config.WebloggerConfig;
    30  import org.apache.roller.weblogger.business.WebloggerFactory;
    31  import org.apache.roller.weblogger.business.UserManager;
    32  import org.apache.roller.weblogger.business.WeblogManager;
    33  import org.apache.roller.weblogger.pojos.ThemeTemplate;
    34  import org.apache.roller.weblogger.pojos.WeblogCategory;
    35  import org.apache.roller.weblogger.pojos.WeblogEntry;
    36  import org.apache.roller.weblogger.pojos.WeblogTemplate;
    37  import org.apache.roller.weblogger.util.URLUtilities;
    38  import org.apache.roller.weblogger.util.Utilities;
    39  
    40  
    41  /**
    42   * Represents a request for a Roller weblog page.
    43   *
    44   * any url from ... /roller-ui/rendering/page/*
    45   *
    46   * We use this class as a helper to parse an incoming url and sort out the
    47   * information embedded in the url for later use.
    48   */
    49  public class WeblogPageRequest extends WeblogRequest {
    50      
             /* 
    P/P       *  Method: org.apache.roller.weblogger.ui.rendering.util.WeblogPageRequest__static_init
              * 
              *  Postconditions:
              *    init'ed(log)
              */
    51      private static Log log = LogFactory.getLog(WeblogPageRequest.class);
    52      
    53      private static final String PAGE_SERVLET = "/roller-ui/rendering/page";
    54      
    55      // lightweight attributes
    56      private String context = null;
    57      private String weblogAnchor = null;
    58      private String weblogPageName = null;
    59      private String weblogCategoryName = null;
    60      private String weblogDate = null;
    61      private List tags = null;
    62      private int pageNum = 0;
    63      private Map customParams = new HashMap();
    64      
    65      // heavyweight attributes
    66      private WeblogEntry weblogEntry = null;
    67      private ThemeTemplate weblogPage = null;
    68      private WeblogCategory weblogCategory = null;
    69      
    70      
             /* 
    P/P       *  Method: void org.apache.roller.weblogger.ui.rendering.util.WeblogPageRequest()
              * 
              *  Postconditions:
              *    this.authenticUser == null
              *    this.context == null
              *    this.locale == null
              *    this.localeInstance == null
              *    this.pathInfo == null
              *    this.request == null
              *    this.tags == null
              *    this.user == null
              *    this.weblog == null
              *    this.weblogAnchor == null
              *    ...
              */
    71      public WeblogPageRequest() {}
    72      
    73      
    74      /**
    75       * Construct the WeblogPageRequest by parsing the incoming url
    76       */
    77      public WeblogPageRequest(HttpServletRequest request) 
    78              throws InvalidRequestException {
    79          
    80          // let our parent take care of their business first
    81          // parent determines weblog handle and locale if specified
                 /* 
    P/P           *  Method: void org.apache.roller.weblogger.ui.rendering.util.WeblogPageRequest(HttpServletRequest)
                  * 
                  *  Preconditions:
                  *    log != null
                  *    org/apache/roller/weblogger/ui/rendering/util/WeblogRequest.log != null
                  *    request != null
                  * 
                  *  Presumptions:
                  *    javax.servlet.http.HttpServletRequest:getParameter(...)@201 == null | org.apache.commons.lang.StringUtils:isNumeric(...)@262 == 0 | java.lang.String:length(...)@262 == 6 | java.lang.String:length(...)@262 == 8
                  *    (!(javax.servlet.http.HttpServletRequest:getParameter(...)@201 == null) & org.apache.commons.lang.StringUtils:isNumeric(...)@262 != 0 & java.lang.String:length(...)@262 == 6) | (!(javax.servlet.http.HttpServletRequest:getParameter(...)@201 == null) & org.a...
                  *    java.lang.String:equals(...)@159 == 1
                  *    java.lang.String:equals(...)@256 == 1
                  *    java.lang.String:equals(...)@74 == 1
                  *    ...
                  * 
                  *  Postconditions:
                  *    java.lang.String:substring(...)._tainted == 0
                  *    java.lang.StringBuilder:toString(...)._tainted == 0
                  *    init'ed(java.lang.StringBuilder:toString(...)._tainted)
                  *    init'ed(this.authenticUser)
                  *    possibly_updated(this.context)
                  *    this.customParams == &new HashMap(WeblogPageRequest#17)
                  *    init'ed(this.locale)
                  *    init'ed(this.localeInstance)
                  *    init'ed(this.pageNum)
                  *    init'ed(this.pathInfo)
                  *    ...
                  * 
                  *  Test Vectors:
                  *    java.lang.String:length(...)@112: {0}, {1..232-1}
                  *    java.lang.String:startsWith(...)@215: {1}, {0}
                  *    javax.servlet.http.HttpServletRequest:getParameter(...)@186: Addr_Set{null}, Inverse{null}
                  *    javax.servlet.http.HttpServletRequest:getParameter(...)@191: Addr_Set{null}, Inverse{null}
                  *    javax.servlet.http.HttpServletRequest:getParameter(...)@200: Addr_Set{null}, Inverse{null}
                  *    javax.servlet.http.HttpServletRequest:getParameter(...)@210: Addr_Set{null}, Inverse{null}
                  *    javax.servlet.http.HttpServletRequest:getParameter(...)@223: Addr_Set{null}, Inverse{null}
                  *    org.apache.commons.lang.StringUtils:isNotEmpty(...)@188: {0}, {1}
                  *    org.apache.commons.lang.StringUtils:isNotEmpty(...)@193: {0}, {1}
                  *    org.apache.commons.logging.Log:isDebugEnabled(...)@243: {0}, {1}
                  */
    82          super(request);
    83          
    84          String servlet = request.getServletPath();
    85          
    86          // we only want the path info left over from after our parents parsing
    87          String pathInfo = this.getPathInfo();
    88          
    89          // parse the request object and figure out what we've got
    90          log.debug("parsing path "+pathInfo);
    91          
    92          // was this request bound for the right servlet?
    93          if(!isValidDestination(servlet)) {
    94              throw new InvalidRequestException("invalid destination for request, "+
    95                      request.getRequestURL());
    96          }
    97          
    98          
    99          /*
   100           * parse path info
   101           *
   102           * we expect one of the following forms of url ...
   103           *
   104           * /entry/<anchor> - permalink
   105           * /date/<YYYYMMDD> - date collection view
   106           * /category/<category> - category collection view
   107           * /tags/<tag>+<tag> - tags
   108           * /page/<pagelink> - custom page
   109           *
   110           * path info may be null, which indicates the weblog homepage
   111           */
   112          if(pathInfo != null && pathInfo.trim().length() > 0) {
   113              
   114              // all views use 2 path elements, except category
   115              String[] pathElements = pathInfo.split("/", 2);
   116              
   117              // the first part of the path always represents the context
+  118              this.context = pathElements[0];
   119              
   120              // now check the rest of the path and extract other details
+  121              if(pathElements.length == 2) {
   122                  
+  123                  if("entry".equals(this.context)) {
   124                      this.weblogAnchor = URLUtilities.decode(pathElements[1]);
   125                      
   126                  } else if("date".equals(this.context)) {
   127                      if(this.isValidDateString(pathElements[1])) {
   128                          this.weblogDate = pathElements[1];
   129                      } else {
   130                          throw new InvalidRequestException("invalid date, "+
   131                              request.getRequestURL());
   132                      }
   133                      
   134                  } else if("category".equals(this.context)) {
   135                      this.weblogCategoryName = URLUtilities.decode(pathElements[1]);
   136                      
   137                      // all categories must start with a /
   138                      if(!this.weblogCategoryName.startsWith("/")) {
   139                          this.weblogCategoryName = "/"+this.weblogCategoryName;
   140                      }
   141                      
   142                  } else if("page".equals(this.context)) {
   143                      this.weblogPageName = pathElements[1];
   144  
   145                  } else if("tags".equals(this.context)) {
   146                      String tagsString = pathElements[1].replace('+', ' ');
   147                      this.tags = Utilities.splitStringAsTags(URLUtilities.decode(tagsString));                  
   148                      int maxSize = WebloggerConfig.getIntProperty("tags.queries.maxIntersectionSize", 3);                  
   149                      if(this.tags.size() > maxSize)
   150                          throw new InvalidRequestException("max number of tags allowed is " + maxSize + ", " + request.getRequestURL());
   151                                        
   152                  } else {
   153                      throw new InvalidRequestException("context "+this.context+
   154                              "not supported, "+request.getRequestURL());
   155                  }
   156                  
   157              } else {
   158                  // empty data is only allowed for the tags section
   159                  if(!"tags".equals(this.context)) {
   160                      throw new InvalidRequestException("invalid index page, "+
   161                              request.getRequestURL());
   162                  }
   163              }
   164              
   165          } else {
   166              // default view, weblog homepage
   167          }
   168          
   169          
   170          /*
   171           * parse request parameters
   172           *
   173           * the only params we currently allow are:
   174           *   date - specifies a weblog date string
   175           *   cat - specifies a weblog category
   176           *   anchor - specifies a weblog entry (old way)
   177           *   entry - specifies a weblog entry
   178           *
   179           * we only allow request params if the path info is null or on user
   180           * defined pages (for backwards compatability).  this way
   181           * we prevent mixing of path based and query param style urls.
   182           */
   183          if(pathInfo == null || this.weblogPageName != null) {
   184              
   185              // check for entry/anchor params which indicate permalink
   186              if(request.getParameter("entry") != null) {
   187                  String anchor = request.getParameter("entry");
   188                  if(StringUtils.isNotEmpty(anchor)) {
   189                      this.weblogAnchor = anchor;
   190                  }
   191              } else if(request.getParameter("anchor") != null) {
   192                  String anchor = request.getParameter("anchor");
   193                  if(StringUtils.isNotEmpty(anchor)) {
   194                      this.weblogAnchor = anchor;
   195                  }
   196              }
   197              
   198              // only check for other params if we didn't find an anchor above or tags
   199              if(this.weblogAnchor == null && this.tags == null) {
   200                  if(request.getParameter("date") != null) {
   201                      String date = request.getParameter("date");
   202                      if(this.isValidDateString(date)) {
   203                          this.weblogDate = date;
   204                      } else {
   205                          throw new InvalidRequestException("invalid date, "+
   206                                  request.getRequestURL());
   207                      }
   208                  }
   209                  
   210                  if(request.getParameter("cat") != null) {
   211                      this.weblogCategoryName =
   212                              URLUtilities.decode(request.getParameter("cat"));
   213                      
   214                      // all categories must start with a /
   215                      if(!this.weblogCategoryName.startsWith("/")) {
   216                          this.weblogCategoryName = "/"+this.weblogCategoryName;
   217                      }
   218                  }
   219              }
   220          }
   221          
   222          // page request param is supported in all views
   223          if(request.getParameter("page") != null) {
   224              String pageInt = request.getParameter("page");
   225              try {
   226                  this.pageNum = Integer.parseInt(pageInt);
   227              } catch(NumberFormatException e) {
   228                  // ignored, bad input
   229              }
   230          }
   231          
   232          // build customParams Map, we remove built-in params because we only
   233          // want this map to represent params defined by the template author
   234          customParams = new HashMap(request.getParameterMap());
   235          customParams.remove("entry");
   236          customParams.remove("anchor");
   237          customParams.remove("date");
   238          customParams.remove("cat");
   239          customParams.remove("page");
   240          customParams.remove("tags");
   241              
   242              
   243          if(log.isDebugEnabled()) {
   244              log.debug("context = "+this.context);
   245              log.debug("weblogAnchor = "+this.weblogAnchor);
   246              log.debug("weblogDate = "+this.weblogDate);
   247              log.debug("weblogCategory = "+this.weblogCategoryName);
   248              log.debug("tags = "+this.tags);
   249              log.debug("weblogPage = "+this.weblogPageName);
   250              log.debug("pageNum = "+this.pageNum);
   251          }
   252      }
   253      
   254      
   255      boolean isValidDestination(String servlet) {
                 /* 
    P/P           *  Method: bool isValidDestination(String)
                  * 
                  *  Postconditions:
                  *    init'ed(return_value)
                  */
   256          return (servlet != null && PAGE_SERVLET.equals(servlet));
   257      }
   258      
   259      
   260      private boolean isValidDateString(String dateString) {
   261          // string must be all numeric and 6 or 8 characters
                 /* 
    P/P           *  Method: bool isValidDateString(String)
                  * 
                  *  Postconditions:
                  *    init'ed(return_value)
                  */
   262          return (dateString != null && StringUtils.isNumeric(dateString) &&
   263                  (dateString.length() == 6 || dateString.length() == 8));
   264      }
   265  
   266      public String getContext() {
                 /* 
    P/P           *  Method: String getContext()
                  * 
                  *  Preconditions:
                  *    init'ed(this.context)
                  * 
                  *  Postconditions:
                  *    return_value == this.context
                  *    init'ed(return_value)
                  */
   267          return context;
   268      }
   269  
   270      public void setContext(String context) {
                 /* 
    P/P           *  Method: void setContext(String)
                  * 
                  *  Postconditions:
                  *    this.context == context
                  *    init'ed(this.context)
                  */
   271          this.context = context;
   272      }
   273  
   274      public String getWeblogAnchor() {
                 /* 
    P/P           *  Method: String getWeblogAnchor()
                  * 
                  *  Preconditions:
                  *    init'ed(this.weblogAnchor)
                  * 
                  *  Postconditions:
                  *    return_value == this.weblogAnchor
                  *    init'ed(return_value)
                  */
   275          return weblogAnchor;
   276      }
   277  
   278      public void setWeblogAnchor(String weblogAnchor) {
                 /* 
    P/P           *  Method: void setWeblogAnchor(String)
                  * 
                  *  Postconditions:
                  *    this.weblogAnchor == weblogAnchor
                  *    init'ed(this.weblogAnchor)
                  */
   279          this.weblogAnchor = weblogAnchor;
   280      }
   281  
   282      public String getWeblogPageName() {
                 /* 
    P/P           *  Method: String getWeblogPageName()
                  * 
                  *  Preconditions:
                  *    init'ed(this.weblogPageName)
                  * 
                  *  Postconditions:
                  *    return_value == this.weblogPageName
                  *    init'ed(return_value)
                  */
   283          return weblogPageName;
   284      }
   285  
   286      public void setWeblogPageName(String weblogPage) {
                 /* 
    P/P           *  Method: void setWeblogPageName(String)
                  * 
                  *  Postconditions:
                  *    this.weblogPageName == weblogPage
                  *    init'ed(this.weblogPageName)
                  */
   287          this.weblogPageName = weblogPage;
   288      }
   289  
   290      public String getWeblogCategoryName() {
                 /* 
    P/P           *  Method: String getWeblogCategoryName()
                  * 
                  *  Preconditions:
                  *    init'ed(this.weblogCategoryName)
                  * 
                  *  Postconditions:
                  *    return_value == this.weblogCategoryName
                  *    init'ed(return_value)
                  */
   291          return weblogCategoryName;
   292      }
   293  
   294      public void setWeblogCategoryName(String weblogCategory) {
                 /* 
    P/P           *  Method: void setWeblogCategoryName(String)
                  * 
                  *  Postconditions:
                  *    this.weblogCategoryName == weblogCategory
                  *    init'ed(this.weblogCategoryName)
                  */
   295          this.weblogCategoryName = weblogCategory;
   296      }
   297  
   298      public String getWeblogDate() {
                 /* 
    P/P           *  Method: String getWeblogDate()
                  * 
                  *  Preconditions:
                  *    init'ed(this.weblogDate)
                  * 
                  *  Postconditions:
                  *    return_value == this.weblogDate
                  *    init'ed(return_value)
                  */
   299          return weblogDate;
   300      }
   301  
   302      public void setWeblogDate(String weblogDate) {
                 /* 
    P/P           *  Method: void setWeblogDate(String)
                  * 
                  *  Postconditions:
                  *    this.weblogDate == weblogDate
                  *    init'ed(this.weblogDate)
                  */
   303          this.weblogDate = weblogDate;
   304      }
   305  
   306      public int getPageNum() {
                 /* 
    P/P           *  Method: int getPageNum()
                  * 
                  *  Preconditions:
                  *    init'ed(this.pageNum)
                  * 
                  *  Postconditions:
                  *    return_value == this.pageNum
                  *    init'ed(return_value)
                  */
   307          return pageNum;
   308      }
   309  
   310      public void setPageNum(int pageNum) {
                 /* 
    P/P           *  Method: void setPageNum(int)
                  * 
                  *  Postconditions:
                  *    this.pageNum == pageNum
                  *    init'ed(this.pageNum)
                  */
   311          this.pageNum = pageNum;
   312      }
   313  
   314      public Map getCustomParams() {
                 /* 
    P/P           *  Method: Map getCustomParams()
                  * 
                  *  Preconditions:
                  *    init'ed(this.customParams)
                  * 
                  *  Postconditions:
                  *    return_value == this.customParams
                  *    init'ed(return_value)
                  */
   315          return customParams;
   316      }
   317  
   318      public void setCustomParams(Map customParams) {
                 /* 
    P/P           *  Method: void setCustomParams(Map)
                  * 
                  *  Postconditions:
                  *    this.customParams == customParams
                  *    init'ed(this.customParams)
                  */
   319          this.customParams = customParams;
   320      }
   321      
   322      public List getTags() {
               /* 
    P/P         *  Method: List getTags()
                * 
                *  Preconditions:
                *    init'ed(this.tags)
                * 
                *  Postconditions:
                *    return_value == this.tags
                *    init'ed(return_value)
                */
   323        return tags;
   324      }
   325      
   326      public void setTags(List tags) {
               /* 
    P/P         *  Method: void setTags(List)
                * 
                *  Postconditions:
                *    this.tags == tags
                *    init'ed(this.tags)
                */
   327        this.tags = tags;
   328      }
   329      
   330      public WeblogEntry getWeblogEntry() {
   331          
                 /* 
    P/P           *  Method: WeblogEntry getWeblogEntry()
                  * 
                  *  Preconditions:
                  *    init'ed(this.weblogEntry)
                  *    (soft) log != null
                  *    (soft) init'ed(this.weblog)
                  *    (soft) org/apache/roller/weblogger/ui/rendering/util/WeblogRequest.log != null
                  *    (soft) init'ed(this.weblogAnchor)
                  *    (soft) init'ed(this.weblogHandle)
                  * 
                  *  Presumptions:
                  *    org.apache.roller.weblogger.business.Weblogger:getWeblogManager(...)@334 != null
                  *    org.apache.roller.weblogger.business.WebloggerFactory:getWeblogger(...)@334 != null
                  * 
                  *  Postconditions:
                  *    init'ed(return_value)
                  *    this.weblogEntry == return_value
                  *    init'ed(this.weblog)
                  * 
                  *  Test Vectors:
                  *    this.weblogEntry: Inverse{null}, Addr_Set{null}
                  *    this.weblogAnchor: Addr_Set{null}, Inverse{null}
                  */
   332          if(weblogEntry == null && weblogAnchor != null) {
   333              try {
   334                  WeblogManager wmgr = WebloggerFactory.getWeblogger().getWeblogManager();
   335                  weblogEntry = wmgr.getWeblogEntryByAnchor(getWeblog(), weblogAnchor);
   336              } catch (WebloggerException ex) {
   337                  log.error("Error getting weblog entry "+weblogAnchor, ex);
   338              }
   339          }
   340          
   341          return weblogEntry;
   342      }
   343  
   344      public void setWeblogEntry(WeblogEntry weblogEntry) {
                 /* 
    P/P           *  Method: void setWeblogEntry(WeblogEntry)
                  * 
                  *  Postconditions:
                  *    this.weblogEntry == weblogEntry
                  *    init'ed(this.weblogEntry)
                  */
   345          this.weblogEntry = weblogEntry;
   346      }
   347  
   348      public ThemeTemplate getWeblogPage() {
   349          
                 /* 
    P/P           *  Method: ThemeTemplate getWeblogPage()
                  * 
                  *  Preconditions:
                  *    init'ed(this.weblogPage)
                  *    (soft) log != null
                  *    (soft) org/apache/roller/weblogger/ui/rendering/util/WeblogRequest.log != null
                  *    (soft) init'ed(this.weblogHandle)
                  *    (soft) init'ed(this.weblogPageName)
                  * 
                  *  Presumptions:
                  *    org.apache.roller.weblogger.pojos.Weblog:getTheme(...)@352 != null
                  * 
                  *  Postconditions:
                  *    init'ed(return_value)
                  *    this.weblogPage == return_value
                  *    possibly_updated(this.weblog)
                  * 
                  *  Test Vectors:
                  *    this.weblogPage: Inverse{null}, Addr_Set{null}
                  *    this.weblogPageName: Addr_Set{null}, Inverse{null}
                  */
   350          if(weblogPage == null && weblogPageName != null) {
   351              try {
+  352                  weblogPage = getWeblog().getTheme().getTemplateByLink(weblogPageName);                
   353              } catch (WebloggerException ex) {
   354                  log.error("Error getting weblog page "+weblogPageName, ex);
   355              }
   356          }
   357          
   358          return weblogPage;
   359      }
   360  
   361      public void setWeblogPage(WeblogTemplate weblogPage) {
                 /* 
    P/P           *  Method: void setWeblogPage(WeblogTemplate)
                  * 
                  *  Postconditions:
                  *    this.weblogPage == weblogPage
                  *    init'ed(this.weblogPage)
                  */
   362          this.weblogPage = weblogPage;
   363      }
   364  
   365      public WeblogCategory getWeblogCategory() {
   366          
                 /* 
    P/P           *  Method: WeblogCategory getWeblogCategory()
                  * 
                  *  Preconditions:
                  *    init'ed(this.weblogCategory)
                  *    (soft) log != null
                  *    (soft) init'ed(this.weblog)
                  *    (soft) org/apache/roller/weblogger/ui/rendering/util/WeblogRequest.log != null
                  *    (soft) init'ed(this.weblogCategoryName)
                  *    (soft) init'ed(this.weblogHandle)
                  * 
                  *  Presumptions:
                  *    org.apache.roller.weblogger.business.Weblogger:getWeblogManager(...)@369 != null
                  *    org.apache.roller.weblogger.business.WebloggerFactory:getWeblogger(...)@369 != null
                  * 
                  *  Postconditions:
                  *    init'ed(return_value)
                  *    this.weblogCategory == return_value
                  *    init'ed(this.weblog)
                  * 
                  *  Test Vectors:
                  *    this.weblogCategory: Inverse{null}, Addr_Set{null}
                  *    this.weblogCategoryName: Addr_Set{null}, Inverse{null}
                  */
   367          if(weblogCategory == null && weblogCategoryName != null) {
   368              try {
   369                  WeblogManager wmgr = WebloggerFactory.getWeblogger().getWeblogManager();
   370                  weblogCategory = wmgr.getWeblogCategoryByPath(getWeblog(), weblogCategoryName);
   371              } catch (WebloggerException ex) {
   372                  log.error("Error getting weblog category "+weblogCategoryName, ex);
   373              }
   374          }
   375          
   376          return weblogCategory;
   377      }
   378  
   379      public void setWeblogCategory(WeblogCategory weblogCategory) {
                 /* 
    P/P           *  Method: void setWeblogCategory(WeblogCategory)
                  * 
                  *  Postconditions:
                  *    this.weblogCategory == weblogCategory
                  *    init'ed(this.weblogCategory)
                  */
   380          this.weblogCategory = weblogCategory;
   381      }
   382      
   383  }








SofCheck Inspector Build Version : 2.18479
WeblogPageRequest.java 2009-Jan-02 14:25:44
WeblogPageRequest.class 2009-Sep-04 03:12:46