]> git.saurik.com Git - apt.git/blob - apt-pkg/contrib/strutl.cc
Simplified time calculations
[apt.git] / apt-pkg / contrib / strutl.cc
1 // -*- mode: cpp; mode: fold -*-
2 // Description /*{{{*/
3 // $Id: strutl.cc,v 1.20 1999/02/27 22:29:11 jgg Exp $
4 /* ######################################################################
5
6 String Util - Some usefull string functions.
7
8 These have been collected from here and there to do all sorts of usefull
9 things to strings. They are usefull in file parsers, URI handlers and
10 especially in APT methods.
11
12 This source is placed in the Public Domain, do with it what you will
13 It was originally written by Jason Gunthorpe <jgg@gpu.srv.ualberta.ca>
14
15 ##################################################################### */
16 /*}}}*/
17 // Includes /*{{{*/
18 #ifdef __GNUG__
19 #pragma implementation "apt-pkg/strutl.h"
20 #endif
21
22 #include <apt-pkg/strutl.h>
23 #include <apt-pkg/fileutl.h>
24
25 #include <ctype.h>
26 #include <string.h>
27 #include <stdio.h>
28 #include <unistd.h>
29 /*}}}*/
30
31 // strstrip - Remove white space from the front and back of a string /*{{{*/
32 // ---------------------------------------------------------------------
33 /* This is handy to use when parsing a file. It also removes \n's left
34 over from fgets and company */
35 char *_strstrip(char *String)
36 {
37 for (;*String != 0 && (*String == ' ' || *String == '\t'); String++);
38
39 if (*String == 0)
40 return String;
41
42 char *End = String + strlen(String) - 1;
43 for (;End != String - 1 && (*End == ' ' || *End == '\t' || *End == '\n' ||
44 *End == '\r'); End--);
45 End++;
46 *End = 0;
47 return String;
48 };
49 /*}}}*/
50 // strtabexpand - Converts tabs into 8 spaces /*{{{*/
51 // ---------------------------------------------------------------------
52 /* */
53 char *_strtabexpand(char *String,size_t Len)
54 {
55 for (char *I = String; I != I + Len && *I != 0; I++)
56 {
57 if (*I != '\t')
58 continue;
59 if (I + 8 > String + Len)
60 {
61 *I = 0;
62 return String;
63 }
64
65 /* Assume the start of the string is 0 and find the next 8 char
66 division */
67 int Len;
68 if (String == I)
69 Len = 1;
70 else
71 Len = 8 - ((String - I) % 8);
72 Len -= 2;
73 if (Len <= 0)
74 {
75 *I = ' ';
76 continue;
77 }
78
79 memmove(I + Len,I + 1,strlen(I) + 1);
80 for (char *J = I; J + Len != I; *I = ' ', I++);
81 }
82 return String;
83 }
84 /*}}}*/
85 // ParseQuoteWord - Parse a single word out of a string /*{{{*/
86 // ---------------------------------------------------------------------
87 /* This grabs a single word, converts any % escaped characters to their
88 proper values and advances the pointer. Double quotes are understood
89 and striped out as well. This is for URI/URL parsing. */
90 bool ParseQuoteWord(const char *&String,string &Res)
91 {
92 // Skip leading whitespace
93 const char *C = String;
94 for (;*C != 0 && *C == ' '; C++);
95 if (*C == 0)
96 return false;
97
98 // Jump to the next word
99 for (;*C != 0 && *C != ' '; C++)
100 {
101 if (*C == '"')
102 {
103 for (C++;*C != 0 && *C != '"'; C++);
104 if (*C == 0)
105 return false;
106 }
107 }
108
109 // Now de-quote characters
110 char Buffer[1024];
111 char Tmp[3];
112 const char *Start = String;
113 char *I;
114 for (I = Buffer; I < Buffer + sizeof(Buffer) && Start != C; I++)
115 {
116 if (*Start == '%' && Start + 2 < C)
117 {
118 Tmp[0] = Start[1];
119 Tmp[1] = Start[2];
120 Tmp[2] = 0;
121 *I = (char)strtol(Tmp,0,16);
122 Start += 3;
123 continue;
124 }
125 if (*Start != '"')
126 *I = *Start;
127 else
128 I--;
129 Start++;
130 }
131 *I = 0;
132 Res = Buffer;
133
134 // Skip ending white space
135 for (;*C != 0 && *C == ' '; C++);
136 String = C;
137 return true;
138 }
139 /*}}}*/
140 // ParseCWord - Parses a string like a C "" expression /*{{{*/
141 // ---------------------------------------------------------------------
142 /* This expects a series of space seperated strings enclosed in ""'s.
143 It concatenates the ""'s into a single string. */
144 bool ParseCWord(const char *String,string &Res)
145 {
146 // Skip leading whitespace
147 const char *C = String;
148 for (;*C != 0 && *C == ' '; C++);
149 if (*C == 0)
150 return false;
151
152 char Buffer[1024];
153 char *Buf = Buffer;
154 if (strlen(String) >= sizeof(Buffer))
155 return false;
156
157 for (; *C != 0; C++)
158 {
159 if (*C == '"')
160 {
161 for (C++; *C != 0 && *C != '"'; C++)
162 *Buf++ = *C;
163
164 if (*C == 0)
165 return false;
166
167 continue;
168 }
169
170 if (C != String && isspace(*C) != 0 && isspace(C[-1]) != 0)
171 continue;
172 if (isspace(*C) == 0)
173 return false;
174 *Buf++ = ' ';
175 }
176 *Buf = 0;
177 Res = Buffer;
178 return true;
179 }
180 /*}}}*/
181 // DeQuoteString - Convert a string from quoted from /*{{{*/
182 // ---------------------------------------------------------------------
183 /* This undoes QuoteString */
184 string DeQuoteString(string Str)
185 {
186 string Res;
187 for (string::iterator I = Str.begin(); I != Str.end(); I++)
188 {
189 if (*I == '%' && I + 2 < Str.end())
190 {
191 char Tmp[3];
192 Tmp[0] = I[1];
193 Tmp[1] = I[2];
194 Tmp[2] = 0;
195 Res += (char)strtol(Tmp,0,16);
196 I += 2;
197 continue;
198 }
199 else
200 Res += *I;
201 }
202 return Res;
203 }
204 /*}}}*/
205 // QuoteString - Convert a string into quoted from /*{{{*/
206 // ---------------------------------------------------------------------
207 /* */
208 string QuoteString(string Str,const char *Bad)
209 {
210 string Res;
211 for (string::iterator I = Str.begin(); I != Str.end(); I++)
212 {
213 if (strchr(Bad,*I) != 0 || isprint(*I) == 0 ||
214 *I <= 0x20 || *I >= 0x7F)
215 {
216 char Buf[10];
217 sprintf(Buf,"%%%02x",(int)*I);
218 Res += Buf;
219 }
220 else
221 Res += *I;
222 }
223 return Res;
224 }
225 /*}}}*/
226 // SizeToStr - Convert a long into a human readable size /*{{{*/
227 // ---------------------------------------------------------------------
228 /* A max of 4 digits are shown before conversion to the next highest unit.
229 The max length of the string will be 5 chars unless the size is > 10
230 YottaBytes (E24) */
231 string SizeToStr(double Size)
232 {
233 char S[300];
234 double ASize;
235 if (Size >= 0)
236 ASize = Size;
237 else
238 ASize = -1*Size;
239
240 /* bytes, KiloBytes, MegaBytes, GigaBytes, TeraBytes, PetaBytes,
241 ExaBytes, ZettaBytes, YottaBytes */
242 char Ext[] = {'\0','k','M','G','T','P','E','Z','Y'};
243 int I = 0;
244 while (I <= 8)
245 {
246 if (ASize < 100 && I != 0)
247 {
248 sprintf(S,"%.1f%c",ASize,Ext[I]);
249 break;
250 }
251
252 if (ASize < 10000)
253 {
254 sprintf(S,"%.0f%c",ASize,Ext[I]);
255 break;
256 }
257 ASize /= 1000.0;
258 I++;
259 }
260
261 return S;
262 }
263 /*}}}*/
264 // TimeToStr - Convert the time into a string /*{{{*/
265 // ---------------------------------------------------------------------
266 /* Converts a number of seconds to a hms format */
267 string TimeToStr(unsigned long Sec)
268 {
269 char S[300];
270
271 while (1)
272 {
273 if (Sec > 60*60*24)
274 {
275 sprintf(S,"%lid %lih%lim%lis",Sec/60/60/24,(Sec/60/60) % 24,(Sec/60) % 60,Sec % 60);
276 break;
277 }
278
279 if (Sec > 60*60)
280 {
281 sprintf(S,"%lih%lim%lis",Sec/60/60,(Sec/60) % 60,Sec % 60);
282 break;
283 }
284
285 if (Sec > 60)
286 {
287 sprintf(S,"%lim%lis",Sec/60,Sec % 60);
288 break;
289 }
290
291 sprintf(S,"%lis",Sec);
292 break;
293 }
294
295 return S;
296 }
297 /*}}}*/
298 // SubstVar - Substitute a string for another string /*{{{*/
299 // ---------------------------------------------------------------------
300 /* This replaces all occurances of Subst with Contents in Str. */
301 string SubstVar(string Str,string Subst,string Contents)
302 {
303 string::size_type Pos = 0;
304 string::size_type OldPos = 0;
305 string Temp;
306
307 while (OldPos < Str.length() &&
308 (Pos = Str.find(Subst,OldPos)) != string::npos)
309 {
310 Temp += string(Str,OldPos,Pos) + Contents;
311 OldPos = Pos + Subst.length();
312 }
313
314 if (OldPos == 0)
315 return Str;
316
317 return Temp + string(Str,OldPos);
318 }
319 /*}}}*/
320 // URItoFileName - Convert the uri into a unique file name /*{{{*/
321 // ---------------------------------------------------------------------
322 /* This converts a URI into a safe filename. It quotes all unsafe characters
323 and converts / to _ and removes the scheme identifier. The resulting
324 file name should be unique and never occur again for a different file */
325 string URItoFileName(string URI)
326 {
327 string::const_iterator I = URI.begin() + URI.find(':') + 1;
328 for (; I < URI.end() && *I == '/'; I++);
329
330 // "\x00-\x20{}|\\\\^\\[\\]<>\"\x7F-\xFF";
331 URI = QuoteString(string(I,URI.end() - I),"\\|{}[]<>\"^~_=!@#$%^&*");
332 string::iterator J = URI.begin();
333 for (; J != URI.end(); J++)
334 if (*J == '/')
335 *J = '_';
336 return URI;
337 }
338 /*}}}*/
339 // Base64Encode - Base64 Encoding routine for short strings /*{{{*/
340 // ---------------------------------------------------------------------
341 /* This routine performs a base64 transformation on a string. It was ripped
342 from wget and then patched and bug fixed.
343
344 This spec can be found in rfc2045 */
345 string Base64Encode(string S)
346 {
347 // Conversion table.
348 static char tbl[64] = {'A','B','C','D','E','F','G','H',
349 'I','J','K','L','M','N','O','P',
350 'Q','R','S','T','U','V','W','X',
351 'Y','Z','a','b','c','d','e','f',
352 'g','h','i','j','k','l','m','n',
353 'o','p','q','r','s','t','u','v',
354 'w','x','y','z','0','1','2','3',
355 '4','5','6','7','8','9','+','/'};
356
357 // Pre-allocate some space
358 string Final;
359 Final.reserve((4*S.length() + 2)/3 + 2);
360
361 /* Transform the 3x8 bits to 4x6 bits, as required by
362 base64. */
363 for (string::const_iterator I = S.begin(); I < S.end(); I += 3)
364 {
365 char Bits[3] = {0,0,0};
366 Bits[0] = I[0];
367 if (I + 1 < S.end())
368 Bits[1] = I[1];
369 if (I + 2 < S.end())
370 Bits[2] = I[2];
371
372 Final += tbl[Bits[0] >> 2];
373 Final += tbl[((Bits[0] & 3) << 4) + (Bits[1] >> 4)];
374
375 if (I + 1 >= S.end())
376 break;
377
378 Final += tbl[((Bits[1] & 0xf) << 2) + (Bits[2] >> 6)];
379
380 if (I + 2 >= S.end())
381 break;
382
383 Final += tbl[Bits[2] & 0x3f];
384 }
385
386 /* Apply the padding elements, this tells how many bytes the remote
387 end should discard */
388 if (S.length() % 3 == 2)
389 Final += '=';
390 if (S.length() % 3 == 1)
391 Final += "==";
392
393 return Final;
394 }
395 /*}}}*/
396 // stringcmp - Arbitary string compare /*{{{*/
397 // ---------------------------------------------------------------------
398 /* This safely compares two non-null terminated strings of arbitary
399 length */
400 int stringcmp(const char *A,const char *AEnd,const char *B,const char *BEnd)
401 {
402 for (; A != AEnd && B != BEnd; A++, B++)
403 if (*A != *B)
404 break;
405
406 if (A == AEnd && B == BEnd)
407 return 0;
408 if (A == AEnd)
409 return 1;
410 if (B == BEnd)
411 return -1;
412 if (*A < *B)
413 return -1;
414 return 1;
415 }
416 /*}}}*/
417 // stringcasecmp - Arbitary case insensitive string compare /*{{{*/
418 // ---------------------------------------------------------------------
419 /* */
420 int stringcasecmp(const char *A,const char *AEnd,const char *B,const char *BEnd)
421 {
422 for (; A != AEnd && B != BEnd; A++, B++)
423 if (toupper(*A) != toupper(*B))
424 break;
425
426 if (A == AEnd && B == BEnd)
427 return 0;
428 if (A == AEnd)
429 return 1;
430 if (B == BEnd)
431 return -1;
432 if (toupper(*A) < toupper(*B))
433 return -1;
434 return 1;
435 }
436 /*}}}*/
437 // LookupTag - Lookup the value of a tag in a taged string /*{{{*/
438 // ---------------------------------------------------------------------
439 /* The format is like those used in package files and the method
440 communication system */
441 string LookupTag(string Message,const char *Tag,const char *Default)
442 {
443 // Look for a matching tag.
444 int Length = strlen(Tag);
445 for (string::iterator I = Message.begin(); I + Length < Message.end(); I++)
446 {
447 // Found the tag
448 if (I[Length] == ':' && stringcasecmp(I,I+Length,Tag) == 0)
449 {
450 // Find the end of line and strip the leading/trailing spaces
451 string::iterator J;
452 I += Length + 1;
453 for (; isspace(*I) != 0 && I < Message.end(); I++);
454 for (J = I; *J != '\n' && J < Message.end(); J++);
455 for (; J > I && isspace(J[-1]) != 0; J--);
456
457 return string(I,J-I);
458 }
459
460 for (; *I != '\n' && I < Message.end(); I++);
461 }
462
463 // Failed to find a match
464 if (Default == 0)
465 return string();
466 return Default;
467 }
468 /*}}}*/
469 // StringToBool - Converts a string into a boolean /*{{{*/
470 // ---------------------------------------------------------------------
471 /* This inspects the string to see if it is true or if it is false and
472 then returns the result. Several varients on true/false are checked. */
473 int StringToBool(string Text,int Default = -1)
474 {
475 char *End;
476 int Res = strtol(Text.c_str(),&End,0);
477 if (End != Text.c_str() && Res >= 0 && Res <= 1)
478 return Res;
479
480 // Check for positives
481 if (strcasecmp(Text.c_str(),"no") == 0 ||
482 strcasecmp(Text.c_str(),"false") == 0 ||
483 strcasecmp(Text.c_str(),"without") == 0 ||
484 strcasecmp(Text.c_str(),"off") == 0 ||
485 strcasecmp(Text.c_str(),"disable") == 0)
486 return 0;
487
488 // Check for negatives
489 if (strcasecmp(Text.c_str(),"yes") == 0 ||
490 strcasecmp(Text.c_str(),"true") == 0 ||
491 strcasecmp(Text.c_str(),"with") == 0 ||
492 strcasecmp(Text.c_str(),"on") == 0 ||
493 strcasecmp(Text.c_str(),"enable") == 0)
494 return 1;
495
496 return Default;
497 }
498 /*}}}*/
499 // TimeRFC1123 - Convert a time_t into RFC1123 format /*{{{*/
500 // ---------------------------------------------------------------------
501 /* This converts a time_t into a string time representation that is
502 year 2000 complient and timezone neutral */
503 string TimeRFC1123(time_t Date)
504 {
505 struct tm Conv = *gmtime(&Date);
506 char Buf[300];
507
508 const char *Day[] = {"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
509 const char *Month[] = {"Jan","Feb","Mar","Apr","May","Jun","Jul",
510 "Aug","Sep","Oct","Nov","Dec"};
511
512 sprintf(Buf,"%s, %02i %s %i %02i:%02i:%02i GMT",Day[Conv.tm_wday],
513 Conv.tm_mday,Month[Conv.tm_mon],Conv.tm_year+1900,Conv.tm_hour,
514 Conv.tm_min,Conv.tm_sec);
515 return Buf;
516 }
517 /*}}}*/
518 // ReadMessages - Read messages from the FD /*{{{*/
519 // ---------------------------------------------------------------------
520 /* This pulls full messages from the input FD into the message buffer.
521 It assumes that messages will not pause during transit so no
522 fancy buffering is used. */
523 bool ReadMessages(int Fd, vector<string> &List)
524 {
525 char Buffer[4000];
526 char *End = Buffer;
527
528 while (1)
529 {
530 int Res = read(Fd,End,sizeof(Buffer) - (End-Buffer));
531
532 // Process is dead, this is kind of bad..
533 if (Res == 0)
534 return false;
535
536 // No data
537 if (Res <= 0)
538 return true;
539
540 End += Res;
541
542 // Look for the end of the message
543 for (char *I = Buffer; I + 1 < End; I++)
544 {
545 if (I[0] != '\n' || I[1] != '\n')
546 continue;
547
548 // Pull the message out
549 string Message(Buffer,0,I-Buffer);
550
551 // Fix up the buffer
552 for (; I < End && *I == '\n'; I++);
553 End -= I-Buffer;
554 memmove(Buffer,I,End-Buffer);
555 I = Buffer;
556
557 List.push_back(Message);
558 }
559 if (End == Buffer)
560 return true;
561
562 if (WaitFd(Fd) == false)
563 return false;
564 }
565 }
566 /*}}}*/
567 // MonthConv - Converts a month string into a number /*{{{*/
568 // ---------------------------------------------------------------------
569 /* This was lifted from the boa webserver which lifted it from 'wn-v1.07'
570 Made it a bit more robust with a few touppers though. */
571 static int MonthConv(char *Month)
572 {
573 switch (toupper(*Month))
574 {
575 case 'A':
576 return toupper(Month[1]) == 'P'?3:7;
577 case 'D':
578 return 11;
579 case 'F':
580 return 1;
581 case 'J':
582 if (toupper(Month[1]) == 'A')
583 return 0;
584 return toupper(Month[2]) == 'N'?5:6;
585 case 'M':
586 return toupper(Month[2]) == 'R'?2:4;
587 case 'N':
588 return 10;
589 case 'O':
590 return 9;
591 case 'S':
592 return 8;
593
594 // Pretend it is January..
595 default:
596 return 0;
597 }
598 }
599 /*}}}*/
600 // StrToTime - Converts a string into a time_t /*{{{*/
601 // ---------------------------------------------------------------------
602 /* This handles all 3 populare time formats including RFC 1123, RFC 1036
603 and the C library asctime format. It requires the GNU library function
604 'timegm' to convert a struct tm in UTC to a time_t. For some bizzar
605 reason the C library does not provide any such function :<*/
606 bool StrToTime(string Val,time_t &Result)
607 {
608 struct tm Tm;
609 char Month[10];
610 const char *I = Val.c_str();
611
612 // Skip the day of the week
613 for (;*I != 0 && *I != ' '; I++);
614
615 // Handle RFC 1123 time
616 if (sscanf(I," %d %3s %d %d:%d:%d GMT",&Tm.tm_mday,Month,&Tm.tm_year,
617 &Tm.tm_hour,&Tm.tm_min,&Tm.tm_sec) != 6)
618 {
619 // Handle RFC 1036 time
620 if (sscanf(I," %d-%3s-%d %d:%d:%d GMT",&Tm.tm_mday,Month,
621 &Tm.tm_year,&Tm.tm_hour,&Tm.tm_min,&Tm.tm_sec) == 6)
622 Tm.tm_year += 1900;
623 else
624 {
625 // asctime format
626 if (sscanf(I," %3s %d %d:%d:%d %d",Month,&Tm.tm_mday,
627 &Tm.tm_hour,&Tm.tm_min,&Tm.tm_sec,&Tm.tm_year) != 6)
628 return false;
629 }
630 }
631
632 Tm.tm_isdst = 0;
633 Tm.tm_mon = MonthConv(Month);
634 Tm.tm_year -= 1900;
635
636 // Convert to local time and then to GMT
637 Result = timegm(&Tm);
638 return true;
639 }
640 /*}}}*/
641
642 // URI::CopyFrom - Copy from an object /*{{{*/
643 // ---------------------------------------------------------------------
644 /* This parses the URI into all of its components */
645 void URI::CopyFrom(string U)
646 {
647 string::const_iterator I = U.begin();
648
649 // Locate the first colon, this seperates the scheme
650 for (; I < U.end() && *I != ':' ; I++);
651 string::const_iterator FirstColon = I;
652
653 /* Determine if this is a host type URI with a leading double //
654 and then search for the first single / */
655 string::const_iterator SingleSlash = I;
656 if (I + 3 < U.end() && I[1] == '/' && I[2] == '/')
657 SingleSlash += 3;
658 for (; SingleSlash < U.end() && *SingleSlash != '/'; SingleSlash++);
659 if (SingleSlash > U.end())
660 SingleSlash = U.end();
661
662 // We can now write the access and path specifiers
663 Access = string(U,0,FirstColon - U.begin());
664 if (SingleSlash != U.end())
665 Path = string(U,SingleSlash - U.begin());
666 if (Path.empty() == true)
667 Path = "/";
668
669 // Now we attempt to locate a user:pass@host fragment
670 if (FirstColon[1] == '/' && FirstColon[2] == '/')
671 FirstColon += 3;
672 else
673 FirstColon += 1;
674 if (FirstColon >= U.end())
675 return;
676
677 if (FirstColon > SingleSlash)
678 FirstColon = SingleSlash;
679
680 // Search for the @
681 I = FirstColon;
682 for (; I < SingleSlash && *I != '@'; I++);
683 string::const_iterator At = I;
684
685 // Colon in the @ section
686 I = FirstColon + 1;
687 for (; I < At && *I != ':'; I++);
688 string::const_iterator SecondColon = I;
689
690 // Now write the host and user/pass
691 if (At == SingleSlash)
692 {
693 if (FirstColon < SingleSlash)
694 Host = string(U,FirstColon - U.begin(),SingleSlash - FirstColon);
695 }
696 else
697 {
698 Host = string(U,At - U.begin() + 1,SingleSlash - At - 1);
699 User = string(U,FirstColon - U.begin(),SecondColon - FirstColon);
700 if (SecondColon < At)
701 Password = string(U,SecondColon - U.begin() + 1,At - SecondColon - 1);
702 }
703
704 // Now we parse off a pot number from the hostname
705 Port = 0;
706 string::size_type Pos = Host.rfind(':');
707 if (Pos == string::npos)
708 return;
709
710 Port = atoi(string(Host,Pos+1).c_str());
711 Host = string(Host,0,Pos);
712 }
713 /*}}}*/
714 // URI::operator string - Convert the URI to a string /*{{{*/
715 // ---------------------------------------------------------------------
716 /* */
717 URI::operator string()
718 {
719 string Res = Access + ':';
720 if (Host.empty() == false)
721 {
722 Res += "//";
723 if (User.empty() == false)
724 {
725 Res += "//" + User;
726 if (Password.empty() == false)
727 Res += ":" + Password;
728 Res += "@";
729 }
730 Res += Host;
731 if (Port != 0)
732 {
733 char S[30];
734 sprintf(S,":%u",Port);
735 Res += S;
736 }
737 }
738
739 if (Path.empty() == false)
740 {
741 if (Path[0] != '/')
742 Res += "/" + Path;
743 else
744 Res += Path;
745 }
746
747 return Res;
748 }
749 /*}}}*/