uucheck.c

Go to the documentation of this file.
00001 /*
00002  * This file is part of uudeview, the simple and friendly multi-part multi-
00003  * file uudecoder program (c) 1994-2001 by Frank Pilhofer. The author may
00004  * be contacted at [email protected]
00005  *
00006  * This program is free software; you can redistribute it and/or modify
00007  * it under the terms of the GNU General Public License as published by
00008  * the Free Software Foundation; either version 2 of the License, or
00009  * (at your option) any later version.
00010  *
00011  * This program is distributed in the hope that it will be useful,
00012  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
00014  * GNU General Public License for more details.
00015  */
00016 
00017 #ifdef HAVE_CONFIG_H
00018 #ifdef _MSC_VER
00019 #include "config.h.win32"
00020 #else
00021 #include "config.h"
00022 #endif
00023 #endif
00024 
00025 #ifdef SYSTEM_WINDLL
00026 #include 
00027 #endif
00028 #ifdef SYSTEM_OS2
00029 #include 
00030 #endif
00031 
00032 /*
00033  * uucheck.c
00034  *
00035  * Various checking and processing of one input part
00036  **/
00037 
00038 #include 
00039 #include 
00040 
00041 #ifdef STDC_HEADERS
00042 #include 
00043 #include 
00044 #endif
00045 #ifdef HAVE_MALLOC_H
00046 #include 
00047 #endif
00048 #ifdef HAVE_UNISTD_H
00049 #include 
00050 #endif
00051 #ifdef HAVE_MEMORY_H
00052 #include 
00053 #endif
00054 
00055 #include <uudeview.h>
00056 #include <uuint.h>
00057 #include <fptools.h>
00058 #include <uustring.h>
00059 
00060 char * uucheck_id = "$Id: uucheck.c 2 2006-10-02 20:45:58Z csk $";
00061 
00062 /*
00063  * Arbitrary number. This is the maximum number of part numbers we
00064  * store for our have-parts and missing-parts lists
00065  */
00066 
00067 #define MAXPLIST 256
00068 
00069 
00070 /*
00071  * forward declarations of local functions
00072  */
00073 
00074 static char *   UUGetFileName   _ANSI_ARGS_((char *, char *, char *));
00075 static int      UUGetPartNo     _ANSI_ARGS_((char *, char **, char **));
00076 
00077 /*
00078  * State of Scanner function and PreProcessPart
00079  */
00080 
00081 int lastvalid, lastenc, nofnum;
00082 char *uucheck_lastname;
00083 char *uucheck_tempname;
00084 static int  lastpart = 0;
00085 static char *nofname = "UNKNOWN";
00086 
00087 /*
00088  * special characters we allow an unquoted filename to have
00089  */
00090 
00091 static char *fnchars = "._-~!";
00092 
00093 /*
00094  * Policy for extracting a part number from the subject line.
00095  * usually, look for part numbers in () brackets first, then in []
00096  */
00097 
00098 static char *brackchr[] = {
00099   "()[]", "[]()"
00100 };
00101 
00102 /*
00103  * Extract a filename from the subject line. We need anything to identify
00104  * the name of the program for sorting. If a nice filename cannot be found, 
00105  * the subject line itself is used
00106  * ptonum is, if not NULL, a pointer to the part number in the subject line,
00107  * so that it won't be used as filename.
00108  **/
00109 
00110 static char *
00111 UUGetFileName (char *subject, char *ptonum, char *ptonend)
00112 {
00113   char *ptr = subject, *iter, *result, *part;
00114   int count, length=0, alflag=0;
00115 
00116 /*
00117  * If this file has no subject line, assume it is the next part of the
00118  * previous file (this is done in UUPreProcessPart)
00119  **/
00120 
00121   if (subject == NULL)
00122     return NULL;
00123 
00124 /*
00125  * If the subject starts with 'Re', it is ignored
00126  * REPosts or RETries are not ignored!
00127  **/
00128 
00129   if (uu_ignreply &&
00130       (subject[0] == 'R' || subject[0] == 'r') &&
00131       (subject[1] == 'E' || subject[1] == 'e') &&
00132       (subject[2] == ':' || subject[2] == ' ')) {
00133     return NULL;
00134   }
00135 
00136 /*
00137  * Ignore a "Repost" prefix of the subject line. We don't want to get
00138  * a file named "Repost" :-)
00139  **/
00140 
00141   if (_FP_strnicmp (subject, "repost", 6) == 0)
00142     subject += 6;
00143   if (_FP_strnicmp (subject, "re:", 3) == 0)
00144     subject += 3;
00145 
00146   while (*subject == ' ' || *subject == ':') subject++;
00147 
00148   part = _FP_stristr (subject, "part");
00149   if (part == subject) {
00150     subject += 4;
00151     while (*subject == ' ') subject++;
00152   }
00153 
00154   /*
00155  * If the file was encoded by uuenview, then the filename is enclosed
00156  * in [brackets]. But check what's inside these bracket's, try not to
00157  * fall for something other than a filename
00158  */
00159 
00160   ptr = subject;
00161   while ((iter = strchr (ptr, '[')) != NULL) {
00162     if (strchr (iter, ']') == NULL) {
00163       ptr = iter + 1;
00164       continue;
00165     }
00166     iter++;
00167     while (isspace ((unsigned char)*iter))
00168       iter++;
00169     count = length = alflag = 0;
00170     while (iter[count] && 
00171            (isalnum ((unsigned char)iter[count]) || strchr (fnchars, iter[count])!=NULL)) {
00172       if (isalpha ((unsigned char)iter[count]))
00173         alflag++;
00174       count++;
00175     }
00176     if (count<4 || alflag==0) {
00177       ptr = iter + 1;
00178       continue;
00179     }
00180     length = count;
00181     while (isspace ((unsigned char)iter[count]))
00182       count++;
00183     if (iter[count] == ']') {
00184       ptr = iter;
00185       break;
00186     }
00187     length = 0;
00188     ptr = iter + 1;
00189   }
00190 
00191   /*
00192  * new filename detection routine, fists mostly for files by ftp-by-email
00193  * servers that create subject lines with ftp.host.address:/full/path/file
00194  * on them. We look for slashes and take the filename from after the last
00195  * one ... or at least we try to.
00196  */
00197 
00198   if (length == 0) {
00199     ptr = subject;
00200     while ((iter = strchr (ptr, '/')) != NULL) {
00201       if (iter >= ptonum && iter <= ptonend) {
00202         ptr = iter + 1;
00203         continue;
00204       }
00205       count = length = 0;
00206       iter++;
00207       while (iter[count] &&
00208              (isalnum((unsigned char)iter[count])||strchr(fnchars, iter[count])!=NULL))
00209         count++;
00210       if (iter[count] == ' ' && length > 4) {
00211         length = count;
00212         break;
00213       }
00214       ptr = iter + ((count)?count:1);
00215     }
00216   }
00217 
00218   /*
00219  * Look for two alphanumeric strings separated by a '.'
00220  * (That's most likely a filename)
00221  **/
00222 
00223   if (length == 0) {
00224     ptr = subject;
00225     while (*ptr && *ptr != 0x0a && *ptr != 0x0d && ptr != part) {
00226       iter  = ptr;
00227       count = length = alflag = 0;
00228       
00229       if (_FP_strnicmp (ptr, "ftp", 3) == 0) {
00230         /* hey, that's an ftp address */
00231         while (isalpha ((unsigned char)*ptr) || isdigit ((unsigned char)*ptr) || *ptr == '.')
00232           ptr++;
00233         continue;
00234       }
00235       
00236       while ((isalnum((unsigned char)*iter)||strchr(fnchars, *iter)!=NULL||
00237               *iter=='/') && *iter && iter != ptonum && *iter != '.') {
00238         if (isalpha ((unsigned char)*iter))
00239           alflag = 1;
00240         
00241         count++; iter++;
00242       }
00243       if (*iter == '\0' || iter == ptonum) {
00244         if (iter == ptonum)
00245           ptr  = ptonend;
00246         else
00247           ptr  = iter;
00248 
00249         length = 0;
00250         continue;
00251       }
00252       if (*iter++ != '.' || count > 32 || alflag == 0) {
00253         ptr    = iter;
00254         length = 0;
00255         continue;
00256       }
00257       if (_FP_strnicmp (iter, "edu", 3) == 0 || 
00258           _FP_strnicmp (iter, "gov", 3) == 0) {
00259         /* hey, that's an ftp address */
00260         while (isalpha ((unsigned char)*iter) || isdigit ((unsigned char)*iter) || *iter == '.')
00261           iter++;
00262         ptr    = iter;
00263         length = 0;
00264         continue;
00265       }
00266       
00267       length += count + 1;
00268       count   = 0;
00269       
00270       while ((isalnum((unsigned char)iter[count])||strchr(fnchars, iter[count])!=NULL||
00271               iter[count]=='/') && iter[count] && iter[count] != '.')
00272         count++;
00273       
00274       if (iter[count]==':' && iter[count+1]=='/') {
00275         /* looks like stuff from a mail server */
00276         ptr = iter + 1;
00277         length = 0;
00278         continue;
00279       }
00280       
00281       if (count > 8 || iter == ptonum) {
00282         ptr    = iter;
00283         length = 0;
00284         continue;
00285       }
00286 
00287       if (iter[count] != '.') {
00288         length += count;
00289         break;
00290       }
00291       
00292       while (iter[count] &&
00293              (isalnum((unsigned char)iter[count])||strchr(fnchars, iter[count])!=NULL||
00294               iter[count]=='/'))
00295         count++;
00296       
00297       if (iter[count]==':' && iter[count+1]=='/') {
00298         /* looks like stuff from a mail server */
00299         ptr = iter + 1;
00300         length = 0;
00301         continue;
00302       }
00303       
00304       if (count < 12 && iter != ptonum) {
00305         length += count;
00306         break;
00307       }
00308 
00309       ptr    = iter;
00310       length = 0;
00311     }
00312   }
00313 
00314   if (length == 0) { /* No filename found, use subject line for ident */
00315     ptr = subject;
00316 
00317     while (*ptr && !isalpha ((unsigned char)*ptr))
00318       ptr++;
00319 
00320     while ((isalnum((unsigned char)ptr[length])||strchr(fnchars,ptr[length])!=NULL||
00321             ptr[length] == '/') && 
00322            ptr[length] && ptr+length!=part && ptr+length!=ptonum)
00323       length++;
00324 
00325     if (length) {
00326       if (ptr[length] == '\0' || ptr[length] == 0x0a || ptr[length] == 0x0d) {
00327         length--;
00328 
00329         /*
00330  * I used to cut off digits from the end of the string, but
00331  * let's try to live without. We want to distinguish
00332  * DUTCH951 from DUTCH952
00333  *
00334  * while ((ptr[length] == ' ' || isdigit ((unsigned char)ptr[length])) && length > 0)
00335  * length--;
00336  */
00337       }
00338       else {
00339         length--;
00340 
00341         while (ptr[length] == ' ' && length > 0)
00342           length--;
00343       }
00344       length++;
00345     }
00346   }
00347 
00348   if (length == 0) { /* Still found nothing? We need *something*! */
00349     ptr    = nofname;
00350     length = strlen (nofname);
00351   }
00352 
00353   if ((result = (char *) malloc (length + 1)) == NULL) {
00354     UUMessage (uucheck_id, __LINE__, UUMSG_ERROR,
00355                uustring (S_OUT_OF_MEMORY), length+1);
00356     return NULL;
00357   }
00358     
00359   memcpy (result, ptr, length);
00360   result[length] = '\0';
00361     
00362   return result;
00363 }
00364 
00365 /*
00366  * Extract the Part Number from the subject line.
00367  * We look first for numbers in (#/#)'s, then for numbers in [#/#]'s
00368  * and then for digits that are not part of a string.
00369  * If we cannot find anything, assume it is the next part of the
00370  * previous file.
00371  * If we find a part number, we put a pointer to it in *where. This is
00372  * done so that the UUGetFileName function doesn't accidentally use the
00373  * part number as the file name. *whend points to the end of this part
00374  * number.
00375  **/
00376 
00377 static int
00378 UUGetPartNo (char *subject, char **where, char **whend)
00379 {
00380   char *ptr = subject, *iter, *delim, bdel[2]=" ";
00381   int count, length=0, bpc;
00382 
00383   *where = NULL; bdel[0] = ' ';
00384   *whend = NULL; bdel[1] = '\0';
00385 
00386   iter  = NULL;
00387   delim = "";
00388 
00389   if (subject == NULL)
00390     return -1;
00391 
00392   if (uu_ignreply &&
00393       (subject[0] == 'R' || subject[0] == 'r') && /* Ignore replies, but not */
00394       (subject[1] == 'E' || subject[1] == 'e') && /* reposts */
00395       (subject[2] == ':' || subject[2] == ' '))
00396     return -2;
00397 
00398   /*
00399  * First try numbers in () or [] (or vice versa, according to bracket
00400  * policy)
00401  */
00402 
00403   for (bpc=0, length=0; brackchr[uu_bracket_policy][bpc]; bpc+=2) {
00404     ptr = subject;
00405     while ((iter = strchr (ptr, brackchr[uu_bracket_policy][bpc])) != NULL) {
00406       count = length = 0; iter++;
00407 
00408       while (*iter == ' ' || *iter == '#')
00409         iter++;
00410 
00411       if (!isdigit ((unsigned char)*iter)) {
00412         ptr = iter;
00413         continue;
00414       }
00415       while (isdigit ((unsigned char)iter[count]))
00416         count++;
00417       length = count;
00418       
00419       if (iter[count] == '\0' || iter[count+1] == '\0') {
00420         iter  += count;
00421         length = 0;
00422         break;
00423       }
00424       if (iter[count] == brackchr[uu_bracket_policy][bpc+1]) {
00425         *where  = iter;
00426         bdel[0] = brackchr[uu_bracket_policy][bpc+1];
00427         delim   = bdel;
00428         break;
00429       }
00430       
00431       while (iter[count] == ' ' || iter[count] == '#' ||
00432              iter[count] == '/' || iter[count] == '\\')  count++;
00433       
00434       if (_FP_strnicmp (iter + count, "of", 2) == 0)
00435         count += 2;
00436       
00437       while (iter[count] == ' ')    count++;
00438       while (isdigit ((unsigned char)iter[count])) count++;
00439       while (iter[count] == ' ')    count++;
00440       
00441       if (iter[count] == brackchr[uu_bracket_policy][bpc+1]) {
00442         *where  = iter;
00443         bdel[0] = brackchr[uu_bracket_policy][bpc+1];
00444         delim   = bdel;
00445         break;
00446       }
00447       
00448       length = 0;
00449       ptr    = iter;
00450     }
00451     if (length)
00452       break;
00453   }
00454 
00455   /*
00456  * look for the string "part " followed by a number
00457  */
00458 
00459   if (length == 0) {
00460     if ((iter = _FP_stristr (subject, "part ")) != NULL) {
00461       iter += 5;
00462 
00463       while (isspace ((unsigned char)*iter) || *iter == '.' || *iter == '-')
00464         iter++;
00465 
00466       while (isdigit ((unsigned char)iter[length]))
00467         length++;
00468 
00469       if (length == 0) {
00470         if (_FP_strnicmp (iter, "one", 3) == 0)        length = 1;
00471         else if (_FP_strnicmp (iter, "two", 3) == 0)   length = 2;
00472         else if (_FP_strnicmp (iter, "three", 5) == 0) length = 3;
00473         else if (_FP_strnicmp (iter, "four",  4) == 0) length = 4;
00474         else if (_FP_strnicmp (iter, "five",  4) == 0) length = 5;
00475         else if (_FP_strnicmp (iter, "six",   3) == 0) length = 6;
00476         else if (_FP_strnicmp (iter, "seven", 5) == 0) length = 7;
00477         else if (_FP_strnicmp (iter, "eight", 5) == 0) length = 8;
00478         else if (_FP_strnicmp (iter, "nine",  4) == 0) length = 9;
00479         else if (_FP_strnicmp (iter, "ten",   3) == 0) length = 10;
00480 
00481         if (length && (*whend = strchr (iter, ' '))) {
00482           *where = iter;
00483           return length;
00484         }
00485         else
00486           length = 0;
00487       }
00488       else {
00489         *where = iter;
00490         delim  = "of";
00491       }
00492     }
00493   }
00494 
00495   /*
00496  * look for the string "part" followed by a number
00497  */
00498 
00499   if (length == 0) {
00500     if ((iter = _FP_stristr (subject, "part")) != NULL) {
00501       iter += 4;
00502 
00503       while (isspace ((unsigned char)*iter) || *iter == '.' || *iter == '-')
00504         iter++;
00505 
00506       while (isdigit ((unsigned char)iter[length]))
00507         length++;
00508 
00509       if (length == 0) {
00510         if (_FP_strnicmp (iter, "one", 3) == 0)        length = 1;
00511         else if (_FP_strnicmp (iter, "two", 3) == 0)   length = 2;
00512         else if (_FP_strnicmp (iter, "three", 5) == 0) length = 3;
00513         else if (_FP_strnicmp (iter, "four",  4) == 0) length = 4;
00514         else if (_FP_strnicmp (iter, "five",  4) == 0) length = 5;
00515         else if (_FP_strnicmp (iter, "six",   3) == 0) length = 6;
00516         else if (_FP_strnicmp (iter, "seven", 5) == 0) length = 7;
00517         else if (_FP_strnicmp (iter, "eight", 5) == 0) length = 8;
00518         else if (_FP_strnicmp (iter, "nine",  4) == 0) length = 9;
00519         else if (_FP_strnicmp (iter, "ten",   3) == 0) length = 10;
00520 
00521         if (length && (*whend = strchr (iter, ' '))) {
00522           *where = iter;
00523           return length;
00524         }
00525         else
00526           length = 0;
00527       }
00528       else {
00529         *where = iter;
00530         delim  = "of";
00531       }
00532     }
00533   }
00534 
00535   /*
00536  * look for [0-9]* "of" [0-9]*
00537  */
00538 
00539   if (length == 0) {
00540     if ((iter = _FP_strirstr (subject, "of")) != NULL) {
00541       while (iter>subject && isspace ((unsigned char)*(iter-1)))
00542         iter--;
00543       if (isdigit((unsigned char)*(iter-1))) {
00544         while (iter>subject && isdigit ((unsigned char)*(iter-1)))
00545           iter--;
00546         if (!isdigit ((unsigned char)*iter) && !isalpha ((unsigned char)*iter) && *iter != '.')
00547           iter++;
00548         ptr = iter;
00549 
00550         while (isdigit ((unsigned char)*ptr)) {
00551           ptr++; length++;
00552         }
00553         *where = iter;
00554         delim  = "of";
00555       }
00556     }
00557   }
00558 
00559   /*
00560  * look for whitespace-separated (or '/'-separated) digits
00561  */
00562 
00563   if (length == 0) {
00564     ptr = subject;
00565 
00566     while (*ptr && length==0) {
00567       while (*ptr && !isdigit ((unsigned char)*ptr))
00568         ptr++;
00569       if (isdigit ((unsigned char)*ptr) && (ptr==subject || *ptr==' ' || *ptr=='/')) {
00570         while (isdigit ((unsigned char)ptr[length]))
00571           length++;
00572         if (ptr[length]!='\0' && ptr[length]!=' ' && ptr[length]!='/') {
00573           ptr   += length;
00574           length = 0;
00575         }
00576         else {
00577           iter    = ptr;
00578           bdel[0] = ptr[length];
00579           delim   = bdel;
00580         }
00581       }
00582       else {
00583         while (isdigit ((unsigned char)*ptr))
00584           ptr++;
00585       }
00586     }
00587   }
00588 
00589   /*
00590  * look for _any_ digits -- currently disabled, because it also fell
00591  * for "part numbers" in file names
00592  */
00593 
00594 #if 0
00595   if (length == 0) {
00596     count = strlen(subject) - 1;
00597     ptr   = subject;
00598  
00599     while (count > 0) {
00600       if (!isdigit((unsigned char)ptr[count])||isalpha((unsigned char)ptr[count+1])||ptr[count+1] == '.') {
00601         count--;
00602         continue;
00603       }
00604       length = 0;
00605 
00606       while (count >= 0 && isdigit ((unsigned char)ptr[count])) {
00607         count--; length++;
00608       }
00609       if (count>=0 && ((isalpha ((unsigned char)ptr[count]) && 
00610                         (ptr[count] != 's' || ptr[count+1] != 't') &&
00611                         (ptr[count] != 'n' || ptr[count+1] != 'd')) || 
00612                        ptr[count] == '/' || ptr[count] == '.' || 
00613                        ptr[count] == '-' || ptr[count] == '_')) {
00614         length = 0;
00615         continue;
00616       }
00617       count++;
00618       iter = ptr + count;
00619 
00620       if (length > 4) {
00621         length = 0;
00622         continue;
00623       }
00624       *where = iter;
00625       delim  = "of";
00626       break;
00627     }
00628   }
00629 #endif
00630 
00631   /*
00632  * look for part numbering as string
00633  */
00634 
00635   if (length == 0) {
00636     /*
00637  * some people use the strangest things, including spelling mistakes :-)
00638  */
00639     if ((iter = _FP_stristr (subject, "first")) != NULL)        length = 1;
00640     else if ((iter = _FP_stristr (subject, "second")) != NULL)  length = 2;
00641     else if ((iter = _FP_stristr (subject, "third")) != NULL)   length = 3;
00642     else if ((iter = _FP_stristr (subject, "forth")) != NULL)   length = 4;
00643     else if ((iter = _FP_stristr (subject, "fourth")) != NULL)  length = 4;
00644     else if ((iter = _FP_stristr (subject, "fifth")) != NULL)   length = 5;
00645     else if ((iter = _FP_stristr (subject, "sixth")) != NULL)   length = 6;
00646     else if ((iter = _FP_stristr (subject, "seventh")) != NULL) length = 7;
00647     else if ((iter = _FP_stristr (subject, "eigth")) != NULL)   length = 8;
00648     else if ((iter = _FP_stristr (subject, "nineth")) != NULL)  length = 9;
00649     else if ((iter = _FP_stristr (subject, "ninth")) != NULL)   length = 9;
00650     else if ((iter = _FP_stristr (subject, "tenth")) != NULL)   length = 10;
00651     else iter = NULL;
00652 
00653     if (length && iter && (*whend = strchr (iter, ' '))) {
00654       *where = iter;
00655       return length;
00656     }
00657     else
00658       length = 0;
00659   }
00660 
00661   if (iter == NULL || length == 0)      /* should be equivalent */
00662     return -1;
00663 
00664   *where = iter;
00665 
00666   if (delim && delim[0]) {
00667     if ((*whend=_FP_stristr (iter, delim)) != NULL && (*whend - *where) < 12) {
00668       ptr = (*whend += strlen (delim));
00669 
00670       while (*ptr == ' ')
00671         ptr++;
00672 
00673       if (isdigit ((unsigned char)*ptr)) {
00674         *whend = ptr;
00675         while (isdigit ((unsigned char)**whend))
00676           *whend += 1;
00677       }
00678     }
00679     else {
00680       *whend = iter + length;
00681     }
00682   }
00683   else {
00684     *whend = iter + length;
00685   }
00686 
00687   return atoi (iter);
00688 }
00689 
00690 /*
00691  * Obtain and process some information about the data.
00692  **/
00693 
00694 uufile *
00695 UUPreProcessPart (fileread *data, int *ret)
00696 {
00697   char *where, *whend, temp[80], *ptr, *p2;
00698   uufile *result;
00699 
00700   if ((result = (uufile *) malloc (sizeof (uufile))) == NULL) {
00701     UUMessage (uucheck_id, __LINE__, UUMSG_ERROR,
00702                uustring (S_OUT_OF_MEMORY), sizeof (uufile));
00703     *ret = UURET_NOMEM;
00704     return NULL;
00705   }
00706   memset (result, 0, sizeof (uufile));
00707 
00708   if (data->partno) {
00709     where = whend  = NULL;
00710     result->partno = data->partno;
00711   }
00712   else if (uu_dumbness) {
00713     result->partno = -1;
00714     where = whend  = NULL;
00715   }
00716   else if ((result->partno=UUGetPartNo(data->subject,&where,&whend)) == -2) {
00717     *ret = UURET_NODATA;
00718     UUkillfile (result);
00719     return NULL;
00720   }
00721 
00722   if (data->filename != NULL) {
00723     if ((result->filename = _FP_strdup (data->filename)) == NULL) {
00724       UUMessage (uucheck_id, __LINE__, UUMSG_ERROR,
00725                  uustring (S_OUT_OF_MEMORY),
00726                  strlen (data->filename)+1);
00727       *ret = UURET_NOMEM;
00728       UUkillfile (result);
00729       return NULL;
00730     }
00731   }
00732   else
00733     result->filename = NULL;
00734 
00735   if (uu_dumbness <= 1)
00736     result->subfname = UUGetFileName (data->subject, where, whend);
00737   else
00738     result->subfname = NULL;
00739 
00740   result->mimeid   = _FP_strdup (data->mimeid);
00741   result->mimetype = _FP_strdup (data->mimetype);
00742 
00743   if (result->partno == -1 && 
00744       (data->uudet == PT_ENCODED || data->uudet == QP_ENCODED))
00745     result->partno = 1;
00746 
00747   if (data->flags & FL_SINGLE) {
00748     /*
00749  * Don't touch this part. But it should really have a filename
00750  */
00751     if (result->filename == NULL) {
00752       sprintf (temp, "%s.%03d", nofname, ++nofnum);
00753       result->filename = _FP_strdup (temp);
00754     }
00755     if (result->subfname == NULL)
00756       result->subfname = _FP_strdup (result->filename);
00757 
00758     if (result->filename == NULL || 
00759         result->subfname == NULL) {
00760       UUMessage (uucheck_id, __LINE__, UUMSG_ERROR,
00761                  uustring (S_OUT_OF_MEMORY),
00762                  (result->filename==NULL)?
00763                  (strlen(temp)+1):(strlen(result->filename)+1));
00764       *ret = UURET_NOMEM;
00765       UUkillfile(result);
00766       return NULL;
00767     }
00768     if (result->partno == -1)
00769       result->partno = 1;
00770   }
00771   else if (result->subfname == NULL && data->uudet &&
00772       (data->begin || result->partno == 1 || 
00773        (!uu_dumbness && result->partno == -1 && 
00774         (data->subject != NULL || result->filename != NULL)))) {
00775     /*
00776  * If it's the first part of something and has some valid data, but
00777  * no subject or anything, initialize lastvalid
00778  */
00779     /*
00780  * in this case, it really _should_ have a filename somewhere
00781  */
00782     if (result->filename != NULL && *result->filename)
00783       result->subfname = _FP_strdup (result->filename);
00784     else { /* if not, escape to UNKNOWN. We need to fill subfname */
00785       sprintf (temp, "%s.%03d", nofname, ++nofnum);
00786       result->subfname = _FP_strdup (temp);
00787     }
00788     /*
00789  * in case the strdup failed
00790  */
00791     if (result->subfname == NULL) {
00792       UUMessage (uucheck_id, __LINE__, UUMSG_ERROR,
00793                  uustring (S_OUT_OF_MEMORY),
00794                  (result->filename)?
00795                  (strlen(result->filename)+1):(strlen(temp)+1));
00796       *ret = UURET_NOMEM;
00797       UUkillfile (result);
00798       return NULL;
00799     }
00800     /*
00801  * if it's also got an 'end', or is the last part in a MIME-Mail,
00802  * then don't set lastvalid
00803  */
00804     if (!data->end && (!data->partno || data->partno != data->maxpno)) {
00805       /*
00806  * initialize lastvalid
00807  */
00808       lastvalid = 1;
00809       lastenc   = data->uudet;
00810       lastpart  = result->partno = 1;
00811       _FP_strncpy (uucheck_lastname, result->subfname, 256);
00812     }
00813     else
00814       result->partno = 1;
00815   }
00816   else if (result->subfname == NULL && data->uudet && data->mimeid) {
00817     /*
00818  * if it's got a file name, use it. Else use the mime-id for identifying
00819  * this part, and hope there's no other files encoded in the same message
00820  * under the same id.
00821  */
00822     if (result->filename)
00823       result->subfname = _FP_strdup (result->filename);
00824     else
00825       result->subfname = _FP_strdup (result->mimeid);
00826   }
00827   else if (result->subfname == NULL && data->uudet) {
00828     /*
00829  * ff we have lastvalid, use it. Make an exception for
00830  * Base64-encoded files.
00831  */
00832     if (data->uudet == B64ENCODED) {
00833       /*
00834  * Assume it's the first part. I wonder why it's got no part number?
00835  */
00836       if (result->filename != NULL && *result->filename)
00837         result->subfname = _FP_strdup (result->filename);
00838       else { /* if not, escape to UNKNOWN. We need to fill subfname */
00839         sprintf (temp, "%s.%03d", nofname, ++nofnum);
00840         result->subfname = _FP_strdup (temp);
00841       }
00842       if (result->subfname == NULL) {
00843         UUMessage (uucheck_id, __LINE__, UUMSG_ERROR,
00844                    uustring (S_OUT_OF_MEMORY),
00845                    (result->filename)?
00846                    (strlen(result->filename)+1):(strlen(temp)+1));
00847         *ret = UURET_NOMEM;
00848         UUkillfile (result);
00849         return NULL;
00850       }
00851       lastvalid = 0;
00852     }
00853     else if (lastvalid && data->uudet == lastenc && result->partno == -1) {
00854       result->subfname = _FP_strdup (uucheck_lastname);
00855       result->partno   = ++lastpart;
00856 
00857       /*
00858  * if it's the last part, invalidate lastvalid
00859  */
00860       if (data->end || (data->partno && data->partno == data->maxpno))
00861         lastvalid = 0;
00862     }
00863     else if (data->partno != -1 && result->filename) {
00864       result->subfname = _FP_strdup (result->filename);
00865     }
00866     else { 
00867       /* 
00868  * it's got no info, it's got no begin, and we don't know anything
00869  * about this part. Let's forget all about it.
00870  */
00871       *ret = UURET_NODATA;
00872       UUkillfile (result);
00873       return NULL;
00874     }
00875   }
00876   else if (result->subfname == NULL && result->partno == -1) {
00877     /*
00878  * This, too, is a part without any useful information that we
00879  * should forget about.
00880  */
00881     *ret = UURET_NODATA;
00882     UUkillfile (result);
00883     return NULL;
00884   }
00885   else if (result->subfname == NULL) {
00886     /*
00887  * This is a part without useful subject name, a valid part number
00888  * but no encoded data. It *could* be the zeroeth part of something,
00889  * but we don't care here. Just forget it.
00890  */
00891     *ret = UURET_NODATA;
00892     UUkillfile (result);
00893     return NULL;
00894   }
00895 
00896   /*
00897  * now, handle some cases where we have a useful subject but no
00898  * useful part number
00899  */
00900 
00901   if (result->partno == -1 && data->begin) {
00902     /*
00903  * hmm, this is reason enough to initialize lastvalid, at least 
00904  * if we have no end
00905  */
00906     if (!data->end) {
00907       _FP_strncpy (uucheck_lastname, result->subfname, 256);
00908       result->partno = lastpart = 1;
00909       lastenc = data->uudet;
00910       lastvalid = 1;
00911     }
00912     else
00913       result->partno = 1;
00914   }
00915   else if (result->partno == -1 && data->uudet) {
00916     if (lastvalid && _FP_stricmp (uucheck_lastname, result->subfname) == 0) {
00917       /*
00918  * if the subject filename is the same as last time, use part no
00919  * of lastvalid. If at end, invalidate lastvalid
00920  */
00921       result->partno = ++lastpart;
00922 
00923       if (data->end)
00924         lastvalid = 0;
00925     }
00926     else {
00927       /*
00928  * data but no part no. It's something UUInsertPartToList() should
00929  * handle
00930  */
00931       goto skipcheck;
00932     }
00933   }
00934   else if (result->partno == -1) {
00935     /*
00936  * it's got no data, so why should we need this one anyway?
00937  */
00938     *ret = UURET_NODATA;
00939     UUkillfile (result);
00940     return NULL;
00941   }
00942 
00943   /*
00944  * at this point, the part should have a valid subfname and a valid
00945  * part number. If it doesn't, then fail.
00946  */
00947   if (result->subfname == NULL || result->partno == -1) {
00948     *ret = UURET_NODATA;
00949     UUkillfile (result);
00950     return NULL;
00951   }
00952 
00953  skipcheck:
00954 
00955   if (result->filename) {
00956     if (*(ptr = _FP_cutdir (result->filename))) {
00957       p2 = _FP_strdup (ptr);
00958       _FP_free (result->filename);
00959       result->filename = p2;
00960     }
00961   }
00962 
00963   result->data = data;
00964   result->NEXT = NULL;
00965 
00966   *ret = UURET_OK;
00967 
00968   return result;
00969 }
00970 
00971 /*
00972  * Insert one part of a file into the global list
00973  **/
00974 
00975 int
00976 UUInsertPartToList (uufile *data)
00977 {
00978   uulist *iter = UUGlobalFileList, *unew;
00979   uufile *fiter, *last;
00980 
00981   /*
00982  * Part belongs together, if
00983  * (1) The MIME-IDs match, or
00984  * (2) The file name received from the subject lines match, and
00985  * (a) Not both parts have a begin line
00986  * (b) Not both parts have an end line
00987  * (c) Both parts don't have different MIME-IDs
00988  * (d) Both parts don't encode different files
00989  * (e) The other part wants to stay alone (FL_SINGLE)
00990  */
00991 
00992   /*
00993  * check if this part wants to be left alone. If so, don't bother
00994  * to do all the checks
00995  */
00996 
00997   while (iter) {
00998     if (data->data->flags & FL_SINGLE) {
00999       /* this space intentionally left blank */
01000     }
01001     else if ((data->mimeid && iter->mimeid &&
01002               strcmp (data->mimeid, iter->mimeid) == 0) ||
01003              (_FP_stricmp (data->subfname, iter->subfname) == 0 &&
01004               !(iter->begin && data->data->begin) &&
01005               !(iter->end   && data->data->end) &&
01006               !(data->mimeid && iter->mimeid &&
01007                 strcmp (data->mimeid, iter->mimeid) != 0) &&
01008               !(data->filename && iter->filename &&
01009                 strcmp (data->filename, iter->filename) != 0) &&
01010               !(iter->flags & FL_SINGLE))) {
01011 
01012       /*
01013  * Don't insert a part that is already there.
01014  *
01015  * Also don't add a part beyond the "end" marker (unless we
01016  * have a mimeid, which screws up the marker).
01017  */
01018 
01019       for (fiter=iter->thisfile; fiter; fiter=fiter->NEXT) {
01020         if (data->partno == fiter->partno)
01021           goto goahead;
01022         if (!iter->mimeid) {
01023           if (data->partno > fiter->partno && fiter->data->end) {
01024             goto goahead;
01025           }
01026         }
01027       }
01028 
01029       if (iter->filename == NULL && data->filename != NULL) {
01030         if ((iter->filename = _FP_strdup (data->filename)) == NULL)
01031           return UURET_NOMEM;
01032       }
01033 
01034       /*
01035  * special case when we might have tagged a part as Base64 when the
01036  * file was really XX
01037  */
01038 
01039       if (data->data->uudet == B64ENCODED && 
01040           iter->uudet == XX_ENCODED && iter->begin) {
01041         data->data->uudet = XX_ENCODED;
01042       }
01043       else if (data->data->uudet == XX_ENCODED && data->data->begin &&
01044                iter->uudet == B64ENCODED) {
01045         iter->uudet = XX_ENCODED;
01046 
01047         fiter = iter->thisfile;
01048         while (fiter) {
01049           fiter->data->uudet = XX_ENCODED;
01050           fiter = fiter->NEXT;
01051         }
01052       }
01053 
01054       /*
01055  * If this is from a Message/Partial, we believe only the
01056  * iter->uudet from the first part
01057  */
01058       if (data->data->flags & FL_PARTIAL) {
01059         if (data->partno == 1) {
01060           iter->uudet = data->data->uudet;
01061           iter->flags = data->data->flags;
01062         }
01063       }
01064       else {
01065         if (data->data->uudet) iter->uudet = data->data->uudet;
01066         if (data->data->flags) iter->flags = data->data->flags;
01067       }
01068 
01069       if (iter->mode == 0 && data->data->mode != 0)
01070         iter->mode = data->data->mode;
01071       if (data->data->begin) iter->begin = (data->partno)?data->partno:1;
01072       if (data->data->end)   iter->end   = (data->partno)?data->partno:1;
01073 
01074       if (data->mimetype) {
01075         _FP_free (iter->mimetype);
01076         iter->mimetype = _FP_strdup (data->mimetype);
01077       }
01078 
01079       /*
01080  * insert part at the beginning
01081  */
01082 
01083       if (data->partno != -1 && data->partno < iter->thisfile->partno) {
01084         iter->state    = UUFILE_READ;
01085         data->NEXT     = iter->thisfile;
01086         iter->thisfile = data;
01087         return UURET_OK;
01088       }
01089 
01090       /*
01091  * insert part somewhere else
01092  */
01093 
01094       iter->state = UUFILE_READ;        /* prepare for re-checking */
01095       fiter       = iter->thisfile;
01096       last        = NULL;
01097 
01098       while (fiter) {
01099         /*
01100  * if we find the same part no again, check which one looks better
01101  */
01102         if (data->partno == fiter->partno) {
01103           if (fiter->data->subject == NULL)
01104             return UURET_NODATA;
01105           else if (_FP_stristr (fiter->data->subject, "repost") != NULL &&
01106                    _FP_stristr (data->data->subject,  "repost") == NULL)
01107             return UURET_NODATA;
01108           else if (fiter->data->uudet && !data->data->uudet)
01109             return UURET_NODATA;
01110           else {
01111             /*
01112  * replace
01113  */
01114             data->NEXT  = fiter->NEXT;
01115             fiter->NEXT = NULL;
01116             UUkillfile (fiter);
01117 
01118             if (last == NULL)
01119               iter->thisfile = data;
01120             else
01121               last->NEXT     = data;
01122 
01123             return UURET_OK;
01124           }
01125         }
01126 
01127         /*
01128  * if at the end of the part list, add it
01129  */
01130 
01131         if (fiter->NEXT == NULL || 
01132             (data->partno != -1 && data->partno < fiter->NEXT->partno)) {
01133           data->NEXT  = fiter->NEXT;
01134           fiter->NEXT = data;
01135 
01136           if (data->partno == -1)
01137             data->partno = fiter->partno + 1;
01138 
01139           return UURET_OK;
01140         }
01141         last  = fiter;
01142         fiter = fiter->NEXT;
01143       }
01144       
01145       return UURET_OK; /* Shouldn't get here */
01146     }
01147   goahead:
01148     /*
01149  * we need iter below
01150  */
01151     if (iter->NEXT == NULL) 
01152       break;
01153 
01154     iter = iter->NEXT;
01155   }
01156   /*
01157  * handle new entry
01158  */
01159 
01160   if (data->partno == -1) {
01161     /*
01162  * if it's got no part no, and it's MIME mail, then assume this is
01163  * part no. 1. If it's not MIME, then we can't handle it; if it
01164  * had a 'begin', it'd have got a part number assigned by
01165  * UUPreProcessPart().
01166  */
01167     if (data->data->uudet == B64ENCODED || data->data->uudet == BH_ENCODED)
01168       data->partno = 1;
01169     else
01170       return UURET_NODATA;
01171   }
01172 
01173   if ((unew = (uulist *) malloc (sizeof (uulist))) == NULL) {
01174     return UURET_NOMEM;
01175   }
01176 
01177   if ((unew->subfname = _FP_strdup (data->subfname)) == NULL) {
01178     _FP_free (unew);
01179     return UURET_NOMEM;
01180   }
01181 
01182   if (data->filename != NULL) {
01183     if ((unew->filename = _FP_strdup (data->filename)) == NULL) {
01184       _FP_free (unew->subfname);
01185       _FP_free (unew);
01186       return UURET_NOMEM;
01187     }
01188   }
01189   else
01190     unew->filename = NULL;
01191 
01192   if (data->mimeid != NULL) {
01193     if ((unew->mimeid = _FP_strdup (data->mimeid)) == NULL) {
01194       _FP_free (unew->subfname);
01195       _FP_free (unew->filename);
01196       _FP_free (unew);
01197       return UURET_NOMEM;
01198     }
01199   }
01200   else
01201     unew->mimeid = NULL;
01202 
01203   if (data->mimetype != NULL) {
01204     if ((unew->mimetype = _FP_strdup (data->mimetype)) == NULL) {
01205       _FP_free (unew->mimeid);
01206       _FP_free (unew->subfname);
01207       _FP_free (unew->filename);
01208       _FP_free (unew);
01209       return UURET_NOMEM;
01210     }
01211   }
01212   else
01213     unew->mimetype = NULL;
01214 
01215   unew->state     = UUFILE_READ;
01216   unew->binfile   = NULL;
01217   unew->thisfile  = data;
01218   unew->mode      = data->data->mode;
01219   unew->uudet     = data->data->uudet;
01220   unew->flags     = data->data->flags;
01221   unew->begin     = (data->data->begin) ? ((data->partno)?data->partno:1) : 0;
01222   unew->end       = (data->data->end)   ? ((data->partno)?data->partno:1) : 0;
01223   unew->misparts  = NULL;
01224   unew->haveparts = NULL;
01225   unew->NEXT      = NULL;
01226 
01227   if (iter == NULL)
01228     UUGlobalFileList = unew;
01229   else
01230     iter->NEXT = unew;
01231 
01232   return UURET_OK;
01233 }
01234 
01235 /*
01236  * At this point, all files are read in and stored in the
01237  * "UUGlobalFileList". Do some checking. All parts there?
01238  **/
01239 
01240 uulist *
01241 UUCheckGlobalList (void)
01242 {
01243   int misparts[MAXPLIST], haveparts[MAXPLIST];
01244   int miscount, havecount, count, flag, part;
01245   uulist *liter=UUGlobalFileList, *prev;
01246   uufile *fiter;
01247   long thesize;
01248 
01249   while (liter) {
01250     miscount = 0;
01251     thesize  = 0;
01252 
01253     if (liter->state & UUFILE_OK) {
01254       liter = liter->NEXT;
01255       continue;
01256     }
01257     else if ((liter->uudet == QP_ENCODED ||
01258               liter->uudet == PT_ENCODED) && 
01259              (liter->flags & FL_SINGLE)) {
01260       if ((liter->flags&FL_PROPER)==0)
01261         liter->size = -1;
01262       else
01263         liter->size = liter->thisfile->data->length;
01264 
01265       liter->state = UUFILE_OK;
01266       continue;
01267     }
01268     else if ((fiter = liter->thisfile) == NULL) {
01269       liter->state = UUFILE_NODATA;
01270       liter = liter->NEXT;
01271       continue;
01272     }
01273 
01274     /*
01275  * Re-Check this file
01276  */
01277 
01278     flag      = 0;
01279     miscount  = 0;
01280     havecount = 0;
01281     thesize   = 0;
01282     liter->state = UUFILE_READ;
01283 
01284     /*
01285  * search encoded data
01286  */
01287 
01288     while (fiter && !fiter->data->uudet) {
01289       if (havecount<MAXPLIST) {
01290         haveparts[havecount++] = fiter->partno;
01291       }
01292       fiter = fiter->NEXT;
01293     }
01294 
01295     if (fiter == NULL) {
01296       liter->state = UUFILE_NODATA;
01297       liter = liter->NEXT;
01298       continue;
01299     }
01300 
01301     if (havecount<MAXPLIST) {
01302       haveparts[havecount++] = fiter->partno;
01303     }
01304 
01305     if ((part = fiter->partno) > 1) {
01306       if (!fiter->data->begin) {
01307         for (count=1; count < part && miscount < MAXPLIST; count++) {
01308           misparts[miscount++] = count;
01309         }
01310       }
01311     }
01312 
01313     /*
01314  * don't care if so many parts are missing
01315  */
01316 
01317     if (miscount >= MAXPLIST) {
01318       liter->state = UUFILE_MISPART;
01319       liter        = liter->NEXT;
01320       continue;
01321     }
01322 
01323     if (liter->uudet == B64ENCODED ||
01324         liter->uudet == QP_ENCODED ||
01325         liter->uudet == PT_ENCODED)
01326       flag |= 3; /* Don't need begin or end with Base64 or plain text*/
01327 
01328     if (fiter->data->begin) flag |= 1;
01329     if (fiter->data->end)   flag |= 2;
01330     if (fiter->data->uudet) flag |= 4; 
01331 
01332     /*
01333  * guess size of part
01334  */
01335 
01336     switch (fiter->data->uudet) {
01337     case UU_ENCODED:
01338     case XX_ENCODED:
01339       thesize += 3*fiter->data->length/4;
01340       thesize -= 3*fiter->data->length/124; /* substract 2 of 62 chars */
01341       break;
01342     case B64ENCODED:
01343       thesize += 3*fiter->data->length/4;
01344       thesize -=  fiter->data->length/52;   /* substract 2 of 78 chars */
01345       break;
01346     case QP_ENCODED:
01347     case PT_ENCODED:
01348       thesize += fiter->data->length;
01349       break;
01350     }
01351       
01352     fiter = fiter->NEXT;
01353 
01354     while (fiter != NULL) {
01355       for (count=part+1; countpartno && miscount<MAXPLIST; count++)
01356         misparts[miscount++] = count;
01357 
01358       part = fiter->partno;
01359       
01360       if (havecount01361         haveparts[havecount++]=part;
01362 
01363       if (fiter->data->begin) flag |= 1;
01364       if (fiter->data->end)   flag |= 2;
01365       if (fiter->data->uudet) flag |= 4;
01366 
01367       switch (fiter->data->uudet) {
01368       case UU_ENCODED:
01369       case XX_ENCODED:
01370         thesize += 3*fiter->data->length/4;
01371         thesize -= 3*fiter->data->length/124; /* substract 2 of 62 chars */
01372         break;
01373       case B64ENCODED:
01374         thesize += 3*fiter->data->length/4;
01375         thesize -=  fiter->data->length/52;   /* substract 2 of 78 chars */
01376         break;
01377       case QP_ENCODED:
01378       case PT_ENCODED:
01379         thesize += fiter->data->length;
01380         break;
01381       }
01382 
01383       if (fiter->data->end)
01384         break;
01385         
01386       fiter = fiter->NEXT;
01387     }
01388 
01389     /*
01390  * if in fast mode, we don't notice an 'end'. So if its uu or xx
01391  * encoded, there's a begin line and encoded data, assume it's
01392  * there.
01393  */
01394     
01395     if (uu_fast_scanning && (flag & 0x01) && (flag & 0x04) &&
01396         (liter->uudet == UU_ENCODED || liter->uudet == XX_ENCODED))
01397       flag |= 2;
01398 
01399     /*
01400  * Set the parts we have and/or missing
01401  */
01402 
01403     _FP_free (liter->haveparts);
01404     _FP_free (liter->misparts);
01405 
01406     liter->haveparts = NULL;
01407     liter->misparts  = NULL;
01408     
01409     if (havecount) {
01410       if ((liter->haveparts=(int*)malloc((havecount+1)*sizeof(int)))!=NULL) {
01411         memcpy (liter->haveparts, haveparts, havecount*sizeof(int));
01412         liter->haveparts[havecount] = 0;
01413       }
01414     }
01415     
01416     if (miscount) {
01417       if ((liter->misparts=(int*)malloc((miscount+1)*sizeof(int)))!=NULL) {
01418         memcpy (liter->misparts, misparts, miscount*sizeof(int));
01419         liter->misparts[miscount] = 0;
01420       }
01421       liter->state |= UUFILE_MISPART;
01422     }
01423 
01424     /*
01425  * Finalize checking
01426  */
01427 
01428     if ((flag & 4) == 0) liter->state |= UUFILE_NODATA;
01429     if ((flag & 1) == 0) liter->state |= UUFILE_NOBEGIN;
01430     if ((flag & 2) == 0) liter->state |= UUFILE_NOEND;
01431     
01432     if ((flag & 7) == 7 && miscount==0) {
01433       liter->state = UUFILE_OK;
01434     }
01435 
01436     if ((uu_fast_scanning && (liter->flags&FL_PROPER)==0) || thesize<=0)
01437       liter->size = -1;
01438     else
01439       liter->size = thesize;
01440 
01441     if (liter->state==UUFILE_OK && 
01442         (liter->filename==NULL || liter->filename[0]=='\0')) {
01443       /*
01444  * Emergency backup if the file does not have a filename
01445  */
01446       _FP_free (liter->filename);
01447       if (liter->subfname && liter->subfname[0] &&
01448           _FP_strpbrk (liter->subfname, "()[];: ") == NULL)
01449         liter->filename = _FP_strdup (liter->subfname);
01450       else {
01451         sprintf (uucheck_tempname, "%s.%03d", nofname, ++nofnum);
01452         liter->filename = _FP_strdup (uucheck_tempname);
01453       }
01454     }
01455     liter = liter->NEXT;
01456   }
01457 
01458   /*
01459  * Sets back (PREV) links
01460  */
01461 
01462   liter = UUGlobalFileList;
01463   prev  = NULL;
01464 
01465   while (liter) {
01466     liter->PREV = prev;
01467     prev        = liter;
01468     liter       = liter->NEXT;
01469   }
01470 
01471   return UUGlobalFileList;
01472 }
01473 

Generated on Sun Oct 12 01:45:30 2008 for NNTPGrab by  1.5.4