| 1 | // -*- mode: cpp; mode: fold -*- |
| 2 | // Description /*{{{*/ |
| 3 | // $Id: strutl.cc,v 1.48 2003/07/18 14:15:11 mdz Exp $ |
| 4 | /* ###################################################################### |
| 5 | |
| 6 | String Util - Some useful string functions. |
| 7 | |
| 8 | These have been collected from here and there to do all sorts of useful |
| 9 | things to strings. They are useful 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 | #include <apt-pkg/error.h> |
| 25 | |
| 26 | #include <apti18n.h> |
| 27 | |
| 28 | #include <ctype.h> |
| 29 | #include <string.h> |
| 30 | #include <stdio.h> |
| 31 | #include <unistd.h> |
| 32 | #include <regex.h> |
| 33 | #include <errno.h> |
| 34 | #include <stdarg.h> |
| 35 | |
| 36 | #include "config.h" |
| 37 | |
| 38 | using namespace std; |
| 39 | /*}}}*/ |
| 40 | |
| 41 | // strstrip - Remove white space from the front and back of a string /*{{{*/ |
| 42 | // --------------------------------------------------------------------- |
| 43 | /* This is handy to use when parsing a file. It also removes \n's left |
| 44 | over from fgets and company */ |
| 45 | char *_strstrip(char *String) |
| 46 | { |
| 47 | for (;*String != 0 && (*String == ' ' || *String == '\t'); String++); |
| 48 | |
| 49 | if (*String == 0) |
| 50 | return String; |
| 51 | |
| 52 | char *End = String + strlen(String) - 1; |
| 53 | for (;End != String - 1 && (*End == ' ' || *End == '\t' || *End == '\n' || |
| 54 | *End == '\r'); End--); |
| 55 | End++; |
| 56 | *End = 0; |
| 57 | return String; |
| 58 | }; |
| 59 | /*}}}*/ |
| 60 | // strtabexpand - Converts tabs into 8 spaces /*{{{*/ |
| 61 | // --------------------------------------------------------------------- |
| 62 | /* */ |
| 63 | char *_strtabexpand(char *String,size_t Len) |
| 64 | { |
| 65 | for (char *I = String; I != I + Len && *I != 0; I++) |
| 66 | { |
| 67 | if (*I != '\t') |
| 68 | continue; |
| 69 | if (I + 8 > String + Len) |
| 70 | { |
| 71 | *I = 0; |
| 72 | return String; |
| 73 | } |
| 74 | |
| 75 | /* Assume the start of the string is 0 and find the next 8 char |
| 76 | division */ |
| 77 | int Len; |
| 78 | if (String == I) |
| 79 | Len = 1; |
| 80 | else |
| 81 | Len = 8 - ((String - I) % 8); |
| 82 | Len -= 2; |
| 83 | if (Len <= 0) |
| 84 | { |
| 85 | *I = ' '; |
| 86 | continue; |
| 87 | } |
| 88 | |
| 89 | memmove(I + Len,I + 1,strlen(I) + 1); |
| 90 | for (char *J = I; J + Len != I; *I = ' ', I++); |
| 91 | } |
| 92 | return String; |
| 93 | } |
| 94 | /*}}}*/ |
| 95 | // ParseQuoteWord - Parse a single word out of a string /*{{{*/ |
| 96 | // --------------------------------------------------------------------- |
| 97 | /* This grabs a single word, converts any % escaped characters to their |
| 98 | proper values and advances the pointer. Double quotes are understood |
| 99 | and striped out as well. This is for URI/URL parsing. It also can |
| 100 | understand [] brackets.*/ |
| 101 | bool ParseQuoteWord(const char *&String,string &Res) |
| 102 | { |
| 103 | // Skip leading whitespace |
| 104 | const char *C = String; |
| 105 | for (;*C != 0 && *C == ' '; C++); |
| 106 | if (*C == 0) |
| 107 | return false; |
| 108 | |
| 109 | // Jump to the next word |
| 110 | for (;*C != 0 && isspace(*C) == 0; C++) |
| 111 | { |
| 112 | if (*C == '"') |
| 113 | { |
| 114 | for (C++; *C != 0 && *C != '"'; C++); |
| 115 | if (*C == 0) |
| 116 | return false; |
| 117 | } |
| 118 | if (*C == '[') |
| 119 | { |
| 120 | for (C++; *C != 0 && *C != ']'; C++); |
| 121 | if (*C == 0) |
| 122 | return false; |
| 123 | } |
| 124 | } |
| 125 | |
| 126 | // Now de-quote characters |
| 127 | char Buffer[1024]; |
| 128 | char Tmp[3]; |
| 129 | const char *Start = String; |
| 130 | char *I; |
| 131 | for (I = Buffer; I < Buffer + sizeof(Buffer) && Start != C; I++) |
| 132 | { |
| 133 | if (*Start == '%' && Start + 2 < C) |
| 134 | { |
| 135 | Tmp[0] = Start[1]; |
| 136 | Tmp[1] = Start[2]; |
| 137 | Tmp[2] = 0; |
| 138 | *I = (char)strtol(Tmp,0,16); |
| 139 | Start += 3; |
| 140 | continue; |
| 141 | } |
| 142 | if (*Start != '"') |
| 143 | *I = *Start; |
| 144 | else |
| 145 | I--; |
| 146 | Start++; |
| 147 | } |
| 148 | *I = 0; |
| 149 | Res = Buffer; |
| 150 | |
| 151 | // Skip ending white space |
| 152 | for (;*C != 0 && isspace(*C) != 0; C++); |
| 153 | String = C; |
| 154 | return true; |
| 155 | } |
| 156 | /*}}}*/ |
| 157 | // ParseCWord - Parses a string like a C "" expression /*{{{*/ |
| 158 | // --------------------------------------------------------------------- |
| 159 | /* This expects a series of space separated strings enclosed in ""'s. |
| 160 | It concatenates the ""'s into a single string. */ |
| 161 | bool ParseCWord(const char *&String,string &Res) |
| 162 | { |
| 163 | // Skip leading whitespace |
| 164 | const char *C = String; |
| 165 | for (;*C != 0 && *C == ' '; C++); |
| 166 | if (*C == 0) |
| 167 | return false; |
| 168 | |
| 169 | char Buffer[1024]; |
| 170 | char *Buf = Buffer; |
| 171 | if (strlen(String) >= sizeof(Buffer)) |
| 172 | return false; |
| 173 | |
| 174 | for (; *C != 0; C++) |
| 175 | { |
| 176 | if (*C == '"') |
| 177 | { |
| 178 | for (C++; *C != 0 && *C != '"'; C++) |
| 179 | *Buf++ = *C; |
| 180 | |
| 181 | if (*C == 0) |
| 182 | return false; |
| 183 | |
| 184 | continue; |
| 185 | } |
| 186 | |
| 187 | if (C != String && isspace(*C) != 0 && isspace(C[-1]) != 0) |
| 188 | continue; |
| 189 | if (isspace(*C) == 0) |
| 190 | return false; |
| 191 | *Buf++ = ' '; |
| 192 | } |
| 193 | *Buf = 0; |
| 194 | Res = Buffer; |
| 195 | String = C; |
| 196 | return true; |
| 197 | } |
| 198 | /*}}}*/ |
| 199 | // QuoteString - Convert a string into quoted from /*{{{*/ |
| 200 | // --------------------------------------------------------------------- |
| 201 | /* */ |
| 202 | string QuoteString(const string &Str, const char *Bad) |
| 203 | { |
| 204 | string Res; |
| 205 | for (string::const_iterator I = Str.begin(); I != Str.end(); I++) |
| 206 | { |
| 207 | if (strchr(Bad,*I) != 0 || isprint(*I) == 0 || |
| 208 | *I <= 0x20 || *I >= 0x7F) |
| 209 | { |
| 210 | char Buf[10]; |
| 211 | sprintf(Buf,"%%%02x",(int)*I); |
| 212 | Res += Buf; |
| 213 | } |
| 214 | else |
| 215 | Res += *I; |
| 216 | } |
| 217 | return Res; |
| 218 | } |
| 219 | /*}}}*/ |
| 220 | // DeQuoteString - Convert a string from quoted from /*{{{*/ |
| 221 | // --------------------------------------------------------------------- |
| 222 | /* This undoes QuoteString */ |
| 223 | string DeQuoteString(const string &Str) |
| 224 | { |
| 225 | string Res; |
| 226 | for (string::const_iterator I = Str.begin(); I != Str.end(); I++) |
| 227 | { |
| 228 | if (*I == '%' && I + 2 < Str.end()) |
| 229 | { |
| 230 | char Tmp[3]; |
| 231 | Tmp[0] = I[1]; |
| 232 | Tmp[1] = I[2]; |
| 233 | Tmp[2] = 0; |
| 234 | Res += (char)strtol(Tmp,0,16); |
| 235 | I += 2; |
| 236 | continue; |
| 237 | } |
| 238 | else |
| 239 | Res += *I; |
| 240 | } |
| 241 | return Res; |
| 242 | } |
| 243 | |
| 244 | /*}}}*/ |
| 245 | // SizeToStr - Convert a long into a human readable size /*{{{*/ |
| 246 | // --------------------------------------------------------------------- |
| 247 | /* A max of 4 digits are shown before conversion to the next highest unit. |
| 248 | The max length of the string will be 5 chars unless the size is > 10 |
| 249 | YottaBytes (E24) */ |
| 250 | string SizeToStr(double Size) |
| 251 | { |
| 252 | char S[300]; |
| 253 | double ASize; |
| 254 | if (Size >= 0) |
| 255 | ASize = Size; |
| 256 | else |
| 257 | ASize = -1*Size; |
| 258 | |
| 259 | /* bytes, KiloBytes, MegaBytes, GigaBytes, TeraBytes, PetaBytes, |
| 260 | ExaBytes, ZettaBytes, YottaBytes */ |
| 261 | char Ext[] = {'\0','k','M','G','T','P','E','Z','Y'}; |
| 262 | int I = 0; |
| 263 | while (I <= 8) |
| 264 | { |
| 265 | if (ASize < 100 && I != 0) |
| 266 | { |
| 267 | sprintf(S,"%.1f%c",ASize,Ext[I]); |
| 268 | break; |
| 269 | } |
| 270 | |
| 271 | if (ASize < 10000) |
| 272 | { |
| 273 | sprintf(S,"%.0f%c",ASize,Ext[I]); |
| 274 | break; |
| 275 | } |
| 276 | ASize /= 1000.0; |
| 277 | I++; |
| 278 | } |
| 279 | |
| 280 | return S; |
| 281 | } |
| 282 | /*}}}*/ |
| 283 | // TimeToStr - Convert the time into a string /*{{{*/ |
| 284 | // --------------------------------------------------------------------- |
| 285 | /* Converts a number of seconds to a hms format */ |
| 286 | string TimeToStr(unsigned long Sec) |
| 287 | { |
| 288 | char S[300]; |
| 289 | |
| 290 | while (1) |
| 291 | { |
| 292 | if (Sec > 60*60*24) |
| 293 | { |
| 294 | sprintf(S,"%lid %lih%lim%lis",Sec/60/60/24,(Sec/60/60) % 24,(Sec/60) % 60,Sec % 60); |
| 295 | break; |
| 296 | } |
| 297 | |
| 298 | if (Sec > 60*60) |
| 299 | { |
| 300 | sprintf(S,"%lih%lim%lis",Sec/60/60,(Sec/60) % 60,Sec % 60); |
| 301 | break; |
| 302 | } |
| 303 | |
| 304 | if (Sec > 60) |
| 305 | { |
| 306 | sprintf(S,"%lim%lis",Sec/60,Sec % 60); |
| 307 | break; |
| 308 | } |
| 309 | |
| 310 | sprintf(S,"%lis",Sec); |
| 311 | break; |
| 312 | } |
| 313 | |
| 314 | return S; |
| 315 | } |
| 316 | /*}}}*/ |
| 317 | // SubstVar - Substitute a string for another string /*{{{*/ |
| 318 | // --------------------------------------------------------------------- |
| 319 | /* This replaces all occurances of Subst with Contents in Str. */ |
| 320 | string SubstVar(const string &Str,const string &Subst,const string &Contents) |
| 321 | { |
| 322 | string::size_type Pos = 0; |
| 323 | string::size_type OldPos = 0; |
| 324 | string Temp; |
| 325 | |
| 326 | while (OldPos < Str.length() && |
| 327 | (Pos = Str.find(Subst,OldPos)) != string::npos) |
| 328 | { |
| 329 | Temp += string(Str,OldPos,Pos) + Contents; |
| 330 | OldPos = Pos + Subst.length(); |
| 331 | } |
| 332 | |
| 333 | if (OldPos == 0) |
| 334 | return Str; |
| 335 | |
| 336 | return Temp + string(Str,OldPos); |
| 337 | } |
| 338 | |
| 339 | string SubstVar(string Str,const struct SubstVar *Vars) |
| 340 | { |
| 341 | for (; Vars->Subst != 0; Vars++) |
| 342 | Str = SubstVar(Str,Vars->Subst,*Vars->Contents); |
| 343 | return Str; |
| 344 | } |
| 345 | /*}}}*/ |
| 346 | // URItoFileName - Convert the uri into a unique file name /*{{{*/ |
| 347 | // --------------------------------------------------------------------- |
| 348 | /* This converts a URI into a safe filename. It quotes all unsafe characters |
| 349 | and converts / to _ and removes the scheme identifier. The resulting |
| 350 | file name should be unique and never occur again for a different file */ |
| 351 | string URItoFileName(const string &URI) |
| 352 | { |
| 353 | // Nuke 'sensitive' items |
| 354 | ::URI U(URI); |
| 355 | U.User.clear(); |
| 356 | U.Password.clear(); |
| 357 | U.Access.clear(); |
| 358 | |
| 359 | // "\x00-\x20{}|\\\\^\\[\\]<>\"\x7F-\xFF"; |
| 360 | string NewURI = QuoteString(U,"\\|{}[]<>\"^~_=!@#$%^&*"); |
| 361 | replace(NewURI.begin(),NewURI.end(),'/','_'); |
| 362 | return NewURI; |
| 363 | } |
| 364 | /*}}}*/ |
| 365 | // Base64Encode - Base64 Encoding routine for short strings /*{{{*/ |
| 366 | // --------------------------------------------------------------------- |
| 367 | /* This routine performs a base64 transformation on a string. It was ripped |
| 368 | from wget and then patched and bug fixed. |
| 369 | |
| 370 | This spec can be found in rfc2045 */ |
| 371 | string Base64Encode(const string &S) |
| 372 | { |
| 373 | // Conversion table. |
| 374 | static char tbl[64] = {'A','B','C','D','E','F','G','H', |
| 375 | 'I','J','K','L','M','N','O','P', |
| 376 | 'Q','R','S','T','U','V','W','X', |
| 377 | 'Y','Z','a','b','c','d','e','f', |
| 378 | 'g','h','i','j','k','l','m','n', |
| 379 | 'o','p','q','r','s','t','u','v', |
| 380 | 'w','x','y','z','0','1','2','3', |
| 381 | '4','5','6','7','8','9','+','/'}; |
| 382 | |
| 383 | // Pre-allocate some space |
| 384 | string Final; |
| 385 | Final.reserve((4*S.length() + 2)/3 + 2); |
| 386 | |
| 387 | /* Transform the 3x8 bits to 4x6 bits, as required by |
| 388 | base64. */ |
| 389 | for (string::const_iterator I = S.begin(); I < S.end(); I += 3) |
| 390 | { |
| 391 | char Bits[3] = {0,0,0}; |
| 392 | Bits[0] = I[0]; |
| 393 | if (I + 1 < S.end()) |
| 394 | Bits[1] = I[1]; |
| 395 | if (I + 2 < S.end()) |
| 396 | Bits[2] = I[2]; |
| 397 | |
| 398 | Final += tbl[Bits[0] >> 2]; |
| 399 | Final += tbl[((Bits[0] & 3) << 4) + (Bits[1] >> 4)]; |
| 400 | |
| 401 | if (I + 1 >= S.end()) |
| 402 | break; |
| 403 | |
| 404 | Final += tbl[((Bits[1] & 0xf) << 2) + (Bits[2] >> 6)]; |
| 405 | |
| 406 | if (I + 2 >= S.end()) |
| 407 | break; |
| 408 | |
| 409 | Final += tbl[Bits[2] & 0x3f]; |
| 410 | } |
| 411 | |
| 412 | /* Apply the padding elements, this tells how many bytes the remote |
| 413 | end should discard */ |
| 414 | if (S.length() % 3 == 2) |
| 415 | Final += '='; |
| 416 | if (S.length() % 3 == 1) |
| 417 | Final += "=="; |
| 418 | |
| 419 | return Final; |
| 420 | } |
| 421 | /*}}}*/ |
| 422 | // stringcmp - Arbitary string compare /*{{{*/ |
| 423 | // --------------------------------------------------------------------- |
| 424 | /* This safely compares two non-null terminated strings of arbitary |
| 425 | length */ |
| 426 | int stringcmp(const char *A,const char *AEnd,const char *B,const char *BEnd) |
| 427 | { |
| 428 | for (; A != AEnd && B != BEnd; A++, B++) |
| 429 | if (*A != *B) |
| 430 | break; |
| 431 | |
| 432 | if (A == AEnd && B == BEnd) |
| 433 | return 0; |
| 434 | if (A == AEnd) |
| 435 | return 1; |
| 436 | if (B == BEnd) |
| 437 | return -1; |
| 438 | if (*A < *B) |
| 439 | return -1; |
| 440 | return 1; |
| 441 | } |
| 442 | |
| 443 | #if __GNUC__ >= 3 |
| 444 | int stringcmp(string::const_iterator A,string::const_iterator AEnd, |
| 445 | const char *B,const char *BEnd) |
| 446 | { |
| 447 | for (; A != AEnd && B != BEnd; A++, B++) |
| 448 | if (*A != *B) |
| 449 | break; |
| 450 | |
| 451 | if (A == AEnd && B == BEnd) |
| 452 | return 0; |
| 453 | if (A == AEnd) |
| 454 | return 1; |
| 455 | if (B == BEnd) |
| 456 | return -1; |
| 457 | if (*A < *B) |
| 458 | return -1; |
| 459 | return 1; |
| 460 | } |
| 461 | int stringcmp(string::const_iterator A,string::const_iterator AEnd, |
| 462 | string::const_iterator B,string::const_iterator BEnd) |
| 463 | { |
| 464 | for (; A != AEnd && B != BEnd; A++, B++) |
| 465 | if (*A != *B) |
| 466 | break; |
| 467 | |
| 468 | if (A == AEnd && B == BEnd) |
| 469 | return 0; |
| 470 | if (A == AEnd) |
| 471 | return 1; |
| 472 | if (B == BEnd) |
| 473 | return -1; |
| 474 | if (*A < *B) |
| 475 | return -1; |
| 476 | return 1; |
| 477 | } |
| 478 | #endif |
| 479 | /*}}}*/ |
| 480 | // stringcasecmp - Arbitary case insensitive string compare /*{{{*/ |
| 481 | // --------------------------------------------------------------------- |
| 482 | /* */ |
| 483 | int stringcasecmp(const char *A,const char *AEnd,const char *B,const char *BEnd) |
| 484 | { |
| 485 | for (; A != AEnd && B != BEnd; A++, B++) |
| 486 | if (toupper(*A) != toupper(*B)) |
| 487 | break; |
| 488 | |
| 489 | if (A == AEnd && B == BEnd) |
| 490 | return 0; |
| 491 | if (A == AEnd) |
| 492 | return 1; |
| 493 | if (B == BEnd) |
| 494 | return -1; |
| 495 | if (toupper(*A) < toupper(*B)) |
| 496 | return -1; |
| 497 | return 1; |
| 498 | } |
| 499 | #if __GNUC__ >= 3 |
| 500 | int stringcasecmp(string::const_iterator A,string::const_iterator AEnd, |
| 501 | const char *B,const char *BEnd) |
| 502 | { |
| 503 | for (; A != AEnd && B != BEnd; A++, B++) |
| 504 | if (toupper(*A) != toupper(*B)) |
| 505 | break; |
| 506 | |
| 507 | if (A == AEnd && B == BEnd) |
| 508 | return 0; |
| 509 | if (A == AEnd) |
| 510 | return 1; |
| 511 | if (B == BEnd) |
| 512 | return -1; |
| 513 | if (toupper(*A) < toupper(*B)) |
| 514 | return -1; |
| 515 | return 1; |
| 516 | } |
| 517 | int stringcasecmp(string::const_iterator A,string::const_iterator AEnd, |
| 518 | string::const_iterator B,string::const_iterator BEnd) |
| 519 | { |
| 520 | for (; A != AEnd && B != BEnd; A++, B++) |
| 521 | if (toupper(*A) != toupper(*B)) |
| 522 | break; |
| 523 | |
| 524 | if (A == AEnd && B == BEnd) |
| 525 | return 0; |
| 526 | if (A == AEnd) |
| 527 | return 1; |
| 528 | if (B == BEnd) |
| 529 | return -1; |
| 530 | if (toupper(*A) < toupper(*B)) |
| 531 | return -1; |
| 532 | return 1; |
| 533 | } |
| 534 | #endif |
| 535 | /*}}}*/ |
| 536 | // LookupTag - Lookup the value of a tag in a taged string /*{{{*/ |
| 537 | // --------------------------------------------------------------------- |
| 538 | /* The format is like those used in package files and the method |
| 539 | communication system */ |
| 540 | string LookupTag(const string &Message,const char *Tag,const char *Default) |
| 541 | { |
| 542 | // Look for a matching tag. |
| 543 | int Length = strlen(Tag); |
| 544 | for (string::const_iterator I = Message.begin(); I + Length < Message.end(); I++) |
| 545 | { |
| 546 | // Found the tag |
| 547 | if (I[Length] == ':' && stringcasecmp(I,I+Length,Tag) == 0) |
| 548 | { |
| 549 | // Find the end of line and strip the leading/trailing spaces |
| 550 | string::const_iterator J; |
| 551 | I += Length + 1; |
| 552 | for (; isspace(*I) != 0 && I < Message.end(); I++); |
| 553 | for (J = I; *J != '\n' && J < Message.end(); J++); |
| 554 | for (; J > I && isspace(J[-1]) != 0; J--); |
| 555 | |
| 556 | return string(I,J); |
| 557 | } |
| 558 | |
| 559 | for (; *I != '\n' && I < Message.end(); I++); |
| 560 | } |
| 561 | |
| 562 | // Failed to find a match |
| 563 | if (Default == 0) |
| 564 | return string(); |
| 565 | return Default; |
| 566 | } |
| 567 | /*}}}*/ |
| 568 | // StringToBool - Converts a string into a boolean /*{{{*/ |
| 569 | // --------------------------------------------------------------------- |
| 570 | /* This inspects the string to see if it is true or if it is false and |
| 571 | then returns the result. Several varients on true/false are checked. */ |
| 572 | int StringToBool(const string &Text,int Default) |
| 573 | { |
| 574 | char *End; |
| 575 | int Res = strtol(Text.c_str(),&End,0); |
| 576 | if (End != Text.c_str() && Res >= 0 && Res <= 1) |
| 577 | return Res; |
| 578 | |
| 579 | // Check for positives |
| 580 | if (strcasecmp(Text.c_str(),"no") == 0 || |
| 581 | strcasecmp(Text.c_str(),"false") == 0 || |
| 582 | strcasecmp(Text.c_str(),"without") == 0 || |
| 583 | strcasecmp(Text.c_str(),"off") == 0 || |
| 584 | strcasecmp(Text.c_str(),"disable") == 0) |
| 585 | return 0; |
| 586 | |
| 587 | // Check for negatives |
| 588 | if (strcasecmp(Text.c_str(),"yes") == 0 || |
| 589 | strcasecmp(Text.c_str(),"true") == 0 || |
| 590 | strcasecmp(Text.c_str(),"with") == 0 || |
| 591 | strcasecmp(Text.c_str(),"on") == 0 || |
| 592 | strcasecmp(Text.c_str(),"enable") == 0) |
| 593 | return 1; |
| 594 | |
| 595 | return Default; |
| 596 | } |
| 597 | /*}}}*/ |
| 598 | // TimeRFC1123 - Convert a time_t into RFC1123 format /*{{{*/ |
| 599 | // --------------------------------------------------------------------- |
| 600 | /* This converts a time_t into a string time representation that is |
| 601 | year 2000 complient and timezone neutral */ |
| 602 | string TimeRFC1123(time_t Date) |
| 603 | { |
| 604 | struct tm Conv = *gmtime(&Date); |
| 605 | char Buf[300]; |
| 606 | |
| 607 | const char *Day[] = {"Sun","Mon","Tue","Wed","Thu","Fri","Sat"}; |
| 608 | const char *Month[] = {"Jan","Feb","Mar","Apr","May","Jun","Jul", |
| 609 | "Aug","Sep","Oct","Nov","Dec"}; |
| 610 | |
| 611 | sprintf(Buf,"%s, %02i %s %i %02i:%02i:%02i GMT",Day[Conv.tm_wday], |
| 612 | Conv.tm_mday,Month[Conv.tm_mon],Conv.tm_year+1900,Conv.tm_hour, |
| 613 | Conv.tm_min,Conv.tm_sec); |
| 614 | return Buf; |
| 615 | } |
| 616 | /*}}}*/ |
| 617 | // ReadMessages - Read messages from the FD /*{{{*/ |
| 618 | // --------------------------------------------------------------------- |
| 619 | /* This pulls full messages from the input FD into the message buffer. |
| 620 | It assumes that messages will not pause during transit so no |
| 621 | fancy buffering is used. */ |
| 622 | bool ReadMessages(int Fd, vector<string> &List) |
| 623 | { |
| 624 | char Buffer[64000]; |
| 625 | char *End = Buffer; |
| 626 | |
| 627 | while (1) |
| 628 | { |
| 629 | int Res = read(Fd,End,sizeof(Buffer) - (End-Buffer)); |
| 630 | if (Res < 0 && errno == EINTR) |
| 631 | continue; |
| 632 | |
| 633 | // Process is dead, this is kind of bad.. |
| 634 | if (Res == 0) |
| 635 | return false; |
| 636 | |
| 637 | // No data |
| 638 | if (Res < 0 && errno == EAGAIN) |
| 639 | return true; |
| 640 | if (Res < 0) |
| 641 | return false; |
| 642 | |
| 643 | End += Res; |
| 644 | |
| 645 | // Look for the end of the message |
| 646 | for (char *I = Buffer; I + 1 < End; I++) |
| 647 | { |
| 648 | if (I[0] != '\n' || I[1] != '\n') |
| 649 | continue; |
| 650 | |
| 651 | // Pull the message out |
| 652 | string Message(Buffer,I-Buffer); |
| 653 | |
| 654 | // Fix up the buffer |
| 655 | for (; I < End && *I == '\n'; I++); |
| 656 | End -= I-Buffer; |
| 657 | memmove(Buffer,I,End-Buffer); |
| 658 | I = Buffer; |
| 659 | |
| 660 | List.push_back(Message); |
| 661 | } |
| 662 | if (End == Buffer) |
| 663 | return true; |
| 664 | |
| 665 | if (WaitFd(Fd) == false) |
| 666 | return false; |
| 667 | } |
| 668 | } |
| 669 | /*}}}*/ |
| 670 | // MonthConv - Converts a month string into a number /*{{{*/ |
| 671 | // --------------------------------------------------------------------- |
| 672 | /* This was lifted from the boa webserver which lifted it from 'wn-v1.07' |
| 673 | Made it a bit more robust with a few touppers though. */ |
| 674 | static int MonthConv(char *Month) |
| 675 | { |
| 676 | switch (toupper(*Month)) |
| 677 | { |
| 678 | case 'A': |
| 679 | return toupper(Month[1]) == 'P'?3:7; |
| 680 | case 'D': |
| 681 | return 11; |
| 682 | case 'F': |
| 683 | return 1; |
| 684 | case 'J': |
| 685 | if (toupper(Month[1]) == 'A') |
| 686 | return 0; |
| 687 | return toupper(Month[2]) == 'N'?5:6; |
| 688 | case 'M': |
| 689 | return toupper(Month[2]) == 'R'?2:4; |
| 690 | case 'N': |
| 691 | return 10; |
| 692 | case 'O': |
| 693 | return 9; |
| 694 | case 'S': |
| 695 | return 8; |
| 696 | |
| 697 | // Pretend it is January.. |
| 698 | default: |
| 699 | return 0; |
| 700 | } |
| 701 | } |
| 702 | /*}}}*/ |
| 703 | // timegm - Internal timegm function if gnu is not available /*{{{*/ |
| 704 | // --------------------------------------------------------------------- |
| 705 | /* Ripped this evil little function from wget - I prefer the use of |
| 706 | GNU timegm if possible as this technique will have interesting problems |
| 707 | with leap seconds, timezones and other. |
| 708 | |
| 709 | Converts struct tm to time_t, assuming the data in tm is UTC rather |
| 710 | than local timezone (mktime assumes the latter). |
| 711 | |
| 712 | Contributed by Roger Beeman <beeman@cisco.com>, with the help of |
| 713 | Mark Baushke <mdb@cisco.com> and the rest of the Gurus at CISCO. */ |
| 714 | |
| 715 | /* Turned it into an autoconf check, because GNU is not the only thing which |
| 716 | can provide timegm. -- 2002-09-22, Joel Baker */ |
| 717 | |
| 718 | #ifndef HAVE_TIMEGM // Now with autoconf! |
| 719 | static time_t timegm(struct tm *t) |
| 720 | { |
| 721 | time_t tl, tb; |
| 722 | |
| 723 | tl = mktime (t); |
| 724 | if (tl == -1) |
| 725 | return -1; |
| 726 | tb = mktime (gmtime (&tl)); |
| 727 | return (tl <= tb ? (tl + (tl - tb)) : (tl - (tb - tl))); |
| 728 | } |
| 729 | #endif |
| 730 | /*}}}*/ |
| 731 | // StrToTime - Converts a string into a time_t /*{{{*/ |
| 732 | // --------------------------------------------------------------------- |
| 733 | /* This handles all 3 populare time formats including RFC 1123, RFC 1036 |
| 734 | and the C library asctime format. It requires the GNU library function |
| 735 | 'timegm' to convert a struct tm in UTC to a time_t. For some bizzar |
| 736 | reason the C library does not provide any such function :< This also |
| 737 | handles the weird, but unambiguous FTP time format*/ |
| 738 | bool StrToTime(const string &Val,time_t &Result) |
| 739 | { |
| 740 | struct tm Tm; |
| 741 | char Month[10]; |
| 742 | const char *I = Val.c_str(); |
| 743 | |
| 744 | // Skip the day of the week |
| 745 | for (;*I != 0 && *I != ' '; I++); |
| 746 | |
| 747 | // Handle RFC 1123 time |
| 748 | Month[0] = 0; |
| 749 | if (sscanf(I," %d %3s %d %d:%d:%d GMT",&Tm.tm_mday,Month,&Tm.tm_year, |
| 750 | &Tm.tm_hour,&Tm.tm_min,&Tm.tm_sec) != 6) |
| 751 | { |
| 752 | // Handle RFC 1036 time |
| 753 | if (sscanf(I," %d-%3s-%d %d:%d:%d GMT",&Tm.tm_mday,Month, |
| 754 | &Tm.tm_year,&Tm.tm_hour,&Tm.tm_min,&Tm.tm_sec) == 6) |
| 755 | Tm.tm_year += 1900; |
| 756 | else |
| 757 | { |
| 758 | // asctime format |
| 759 | if (sscanf(I," %3s %d %d:%d:%d %d",Month,&Tm.tm_mday, |
| 760 | &Tm.tm_hour,&Tm.tm_min,&Tm.tm_sec,&Tm.tm_year) != 6) |
| 761 | { |
| 762 | // 'ftp' time |
| 763 | if (sscanf(Val.c_str(),"%4d%2d%2d%2d%2d%2d",&Tm.tm_year,&Tm.tm_mon, |
| 764 | &Tm.tm_mday,&Tm.tm_hour,&Tm.tm_min,&Tm.tm_sec) != 6) |
| 765 | return false; |
| 766 | Tm.tm_mon--; |
| 767 | } |
| 768 | } |
| 769 | } |
| 770 | |
| 771 | Tm.tm_isdst = 0; |
| 772 | if (Month[0] != 0) |
| 773 | Tm.tm_mon = MonthConv(Month); |
| 774 | Tm.tm_year -= 1900; |
| 775 | |
| 776 | // Convert to local time and then to GMT |
| 777 | Result = timegm(&Tm); |
| 778 | return true; |
| 779 | } |
| 780 | /*}}}*/ |
| 781 | // StrToNum - Convert a fixed length string to a number /*{{{*/ |
| 782 | // --------------------------------------------------------------------- |
| 783 | /* This is used in decoding the crazy fixed length string headers in |
| 784 | tar and ar files. */ |
| 785 | bool StrToNum(const char *Str,unsigned long &Res,unsigned Len,unsigned Base) |
| 786 | { |
| 787 | char S[30]; |
| 788 | if (Len >= sizeof(S)) |
| 789 | return false; |
| 790 | memcpy(S,Str,Len); |
| 791 | S[Len] = 0; |
| 792 | |
| 793 | // All spaces is a zero |
| 794 | Res = 0; |
| 795 | unsigned I; |
| 796 | for (I = 0; S[I] == ' '; I++); |
| 797 | if (S[I] == 0) |
| 798 | return true; |
| 799 | |
| 800 | char *End; |
| 801 | Res = strtoul(S,&End,Base); |
| 802 | if (End == S) |
| 803 | return false; |
| 804 | |
| 805 | return true; |
| 806 | } |
| 807 | /*}}}*/ |
| 808 | // HexDigit - Convert a hex character into an integer /*{{{*/ |
| 809 | // --------------------------------------------------------------------- |
| 810 | /* Helper for Hex2Num */ |
| 811 | static int HexDigit(int c) |
| 812 | { |
| 813 | if (c >= '0' && c <= '9') |
| 814 | return c - '0'; |
| 815 | if (c >= 'a' && c <= 'f') |
| 816 | return c - 'a' + 10; |
| 817 | if (c >= 'A' && c <= 'F') |
| 818 | return c - 'A' + 10; |
| 819 | return 0; |
| 820 | } |
| 821 | /*}}}*/ |
| 822 | // Hex2Num - Convert a long hex number into a buffer /*{{{*/ |
| 823 | // --------------------------------------------------------------------- |
| 824 | /* The length of the buffer must be exactly 1/2 the length of the string. */ |
| 825 | bool Hex2Num(const string &Str,unsigned char *Num,unsigned int Length) |
| 826 | { |
| 827 | if (Str.length() != Length*2) |
| 828 | return false; |
| 829 | |
| 830 | // Convert each digit. We store it in the same order as the string |
| 831 | int J = 0; |
| 832 | for (string::const_iterator I = Str.begin(); I != Str.end();J++, I += 2) |
| 833 | { |
| 834 | if (isxdigit(*I) == 0 || isxdigit(I[1]) == 0) |
| 835 | return false; |
| 836 | |
| 837 | Num[J] = HexDigit(I[0]) << 4; |
| 838 | Num[J] += HexDigit(I[1]); |
| 839 | } |
| 840 | |
| 841 | return true; |
| 842 | } |
| 843 | /*}}}*/ |
| 844 | // TokSplitString - Split a string up by a given token /*{{{*/ |
| 845 | // --------------------------------------------------------------------- |
| 846 | /* This is intended to be a faster splitter, it does not use dynamic |
| 847 | memories. Input is changed to insert nulls at each token location. */ |
| 848 | bool TokSplitString(char Tok,char *Input,char **List, |
| 849 | unsigned long ListMax) |
| 850 | { |
| 851 | // Strip any leading spaces |
| 852 | char *Start = Input; |
| 853 | char *Stop = Start + strlen(Start); |
| 854 | for (; *Start != 0 && isspace(*Start) != 0; Start++); |
| 855 | |
| 856 | unsigned long Count = 0; |
| 857 | char *Pos = Start; |
| 858 | while (Pos != Stop) |
| 859 | { |
| 860 | // Skip to the next Token |
| 861 | for (; Pos != Stop && *Pos != Tok; Pos++); |
| 862 | |
| 863 | // Back remove spaces |
| 864 | char *End = Pos; |
| 865 | for (; End > Start && (End[-1] == Tok || isspace(End[-1]) != 0); End--); |
| 866 | *End = 0; |
| 867 | |
| 868 | List[Count++] = Start; |
| 869 | if (Count >= ListMax) |
| 870 | { |
| 871 | List[Count-1] = 0; |
| 872 | return false; |
| 873 | } |
| 874 | |
| 875 | // Advance pos |
| 876 | for (; Pos != Stop && (*Pos == Tok || isspace(*Pos) != 0 || *Pos == 0); Pos++); |
| 877 | Start = Pos; |
| 878 | } |
| 879 | |
| 880 | List[Count] = 0; |
| 881 | return true; |
| 882 | } |
| 883 | /*}}}*/ |
| 884 | // RegexChoice - Simple regex list/list matcher /*{{{*/ |
| 885 | // --------------------------------------------------------------------- |
| 886 | /* */ |
| 887 | unsigned long RegexChoice(RxChoiceList *Rxs,const char **ListBegin, |
| 888 | const char **ListEnd) |
| 889 | { |
| 890 | for (RxChoiceList *R = Rxs; R->Str != 0; R++) |
| 891 | R->Hit = false; |
| 892 | |
| 893 | unsigned long Hits = 0; |
| 894 | for (; ListBegin != ListEnd; ListBegin++) |
| 895 | { |
| 896 | // Check if the name is a regex |
| 897 | const char *I; |
| 898 | bool Regex = true; |
| 899 | for (I = *ListBegin; *I != 0; I++) |
| 900 | if (*I == '.' || *I == '?' || *I == '*' || *I == '|') |
| 901 | break; |
| 902 | if (*I == 0) |
| 903 | Regex = false; |
| 904 | |
| 905 | // Compile the regex pattern |
| 906 | regex_t Pattern; |
| 907 | if (Regex == true) |
| 908 | if (regcomp(&Pattern,*ListBegin,REG_EXTENDED | REG_ICASE | |
| 909 | REG_NOSUB) != 0) |
| 910 | Regex = false; |
| 911 | |
| 912 | // Search the list |
| 913 | bool Done = false; |
| 914 | for (RxChoiceList *R = Rxs; R->Str != 0; R++) |
| 915 | { |
| 916 | if (R->Str[0] == 0) |
| 917 | continue; |
| 918 | |
| 919 | if (strcasecmp(R->Str,*ListBegin) != 0) |
| 920 | { |
| 921 | if (Regex == false) |
| 922 | continue; |
| 923 | if (regexec(&Pattern,R->Str,0,0,0) != 0) |
| 924 | continue; |
| 925 | } |
| 926 | Done = true; |
| 927 | |
| 928 | if (R->Hit == false) |
| 929 | Hits++; |
| 930 | |
| 931 | R->Hit = true; |
| 932 | } |
| 933 | |
| 934 | if (Regex == true) |
| 935 | regfree(&Pattern); |
| 936 | |
| 937 | if (Done == false) |
| 938 | _error->Warning(_("Selection %s not found"),*ListBegin); |
| 939 | } |
| 940 | |
| 941 | return Hits; |
| 942 | } |
| 943 | /*}}}*/ |
| 944 | // ioprintf - C format string outputter to C++ iostreams /*{{{*/ |
| 945 | // --------------------------------------------------------------------- |
| 946 | /* This is used to make the internationalization strings easier to translate |
| 947 | and to allow reordering of parameters */ |
| 948 | void ioprintf(ostream &out,const char *format,...) |
| 949 | { |
| 950 | va_list args; |
| 951 | va_start(args,format); |
| 952 | |
| 953 | // sprintf the description |
| 954 | char S[400]; |
| 955 | vsnprintf(S,sizeof(S),format,args); |
| 956 | out << S; |
| 957 | } |
| 958 | /*}}}*/ |
| 959 | // safe_snprintf - Safer snprintf /*{{{*/ |
| 960 | // --------------------------------------------------------------------- |
| 961 | /* This is a snprintf that will never (ever) go past 'End' and returns a |
| 962 | pointer to the end of the new string. The returned string is always null |
| 963 | terminated unless Buffer == end. This is a better alterantive to using |
| 964 | consecutive snprintfs. */ |
| 965 | char *safe_snprintf(char *Buffer,char *End,const char *Format,...) |
| 966 | { |
| 967 | va_list args; |
| 968 | unsigned long Did; |
| 969 | |
| 970 | va_start(args,Format); |
| 971 | |
| 972 | if (End <= Buffer) |
| 973 | return End; |
| 974 | |
| 975 | Did = vsnprintf(Buffer,End - Buffer,Format,args); |
| 976 | if (Did < 0 || Buffer + Did > End) |
| 977 | return End; |
| 978 | return Buffer + Did; |
| 979 | } |
| 980 | /*}}}*/ |
| 981 | |
| 982 | // CheckDomainList - See if Host is in a , seperate list /*{{{*/ |
| 983 | // --------------------------------------------------------------------- |
| 984 | /* The domain list is a comma seperate list of domains that are suffix |
| 985 | matched against the argument */ |
| 986 | bool CheckDomainList(const string &Host,const string &List) |
| 987 | { |
| 988 | string::const_iterator Start = List.begin(); |
| 989 | for (string::const_iterator Cur = List.begin(); Cur <= List.end(); Cur++) |
| 990 | { |
| 991 | if (Cur < List.end() && *Cur != ',') |
| 992 | continue; |
| 993 | |
| 994 | // Match the end of the string.. |
| 995 | if ((Host.size() >= (unsigned)(Cur - Start)) && |
| 996 | Cur - Start != 0 && |
| 997 | stringcasecmp(Host.end() - (Cur - Start),Host.end(),Start,Cur) == 0) |
| 998 | return true; |
| 999 | |
| 1000 | Start = Cur + 1; |
| 1001 | } |
| 1002 | return false; |
| 1003 | } |
| 1004 | /*}}}*/ |
| 1005 | |
| 1006 | // URI::CopyFrom - Copy from an object /*{{{*/ |
| 1007 | // --------------------------------------------------------------------- |
| 1008 | /* This parses the URI into all of its components */ |
| 1009 | void URI::CopyFrom(const string &U) |
| 1010 | { |
| 1011 | string::const_iterator I = U.begin(); |
| 1012 | |
| 1013 | // Locate the first colon, this separates the scheme |
| 1014 | for (; I < U.end() && *I != ':' ; I++); |
| 1015 | string::const_iterator FirstColon = I; |
| 1016 | |
| 1017 | /* Determine if this is a host type URI with a leading double // |
| 1018 | and then search for the first single / */ |
| 1019 | string::const_iterator SingleSlash = I; |
| 1020 | if (I + 3 < U.end() && I[1] == '/' && I[2] == '/') |
| 1021 | SingleSlash += 3; |
| 1022 | |
| 1023 | /* Find the / indicating the end of the hostname, ignoring /'s in the |
| 1024 | square brackets */ |
| 1025 | bool InBracket = false; |
| 1026 | for (; SingleSlash < U.end() && (*SingleSlash != '/' || InBracket == true); SingleSlash++) |
| 1027 | { |
| 1028 | if (*SingleSlash == '[') |
| 1029 | InBracket = true; |
| 1030 | if (InBracket == true && *SingleSlash == ']') |
| 1031 | InBracket = false; |
| 1032 | } |
| 1033 | |
| 1034 | if (SingleSlash > U.end()) |
| 1035 | SingleSlash = U.end(); |
| 1036 | |
| 1037 | // We can now write the access and path specifiers |
| 1038 | Access.assign(U.begin(),FirstColon); |
| 1039 | if (SingleSlash != U.end()) |
| 1040 | Path.assign(SingleSlash,U.end()); |
| 1041 | if (Path.empty() == true) |
| 1042 | Path = "/"; |
| 1043 | |
| 1044 | // Now we attempt to locate a user:pass@host fragment |
| 1045 | if (FirstColon + 2 <= U.end() && FirstColon[1] == '/' && FirstColon[2] == '/') |
| 1046 | FirstColon += 3; |
| 1047 | else |
| 1048 | FirstColon += 1; |
| 1049 | if (FirstColon >= U.end()) |
| 1050 | return; |
| 1051 | |
| 1052 | if (FirstColon > SingleSlash) |
| 1053 | FirstColon = SingleSlash; |
| 1054 | |
| 1055 | // Find the colon... |
| 1056 | I = FirstColon + 1; |
| 1057 | if (I > SingleSlash) |
| 1058 | I = SingleSlash; |
| 1059 | for (; I < SingleSlash && *I != ':'; I++); |
| 1060 | string::const_iterator SecondColon = I; |
| 1061 | |
| 1062 | // Search for the @ after the colon |
| 1063 | for (; I < SingleSlash && *I != '@'; I++); |
| 1064 | string::const_iterator At = I; |
| 1065 | |
| 1066 | // Now write the host and user/pass |
| 1067 | if (At == SingleSlash) |
| 1068 | { |
| 1069 | if (FirstColon < SingleSlash) |
| 1070 | Host.assign(FirstColon,SingleSlash); |
| 1071 | } |
| 1072 | else |
| 1073 | { |
| 1074 | Host.assign(At+1,SingleSlash); |
| 1075 | User.assign(FirstColon,SecondColon); |
| 1076 | if (SecondColon < At) |
| 1077 | Password.assign(SecondColon+1,At); |
| 1078 | } |
| 1079 | |
| 1080 | // Now we parse the RFC 2732 [] hostnames. |
| 1081 | unsigned long PortEnd = 0; |
| 1082 | InBracket = false; |
| 1083 | for (unsigned I = 0; I != Host.length();) |
| 1084 | { |
| 1085 | if (Host[I] == '[') |
| 1086 | { |
| 1087 | InBracket = true; |
| 1088 | Host.erase(I,1); |
| 1089 | continue; |
| 1090 | } |
| 1091 | |
| 1092 | if (InBracket == true && Host[I] == ']') |
| 1093 | { |
| 1094 | InBracket = false; |
| 1095 | Host.erase(I,1); |
| 1096 | PortEnd = I; |
| 1097 | continue; |
| 1098 | } |
| 1099 | I++; |
| 1100 | } |
| 1101 | |
| 1102 | // Tsk, weird. |
| 1103 | if (InBracket == true) |
| 1104 | { |
| 1105 | Host.clear(); |
| 1106 | return; |
| 1107 | } |
| 1108 | |
| 1109 | // Now we parse off a port number from the hostname |
| 1110 | Port = 0; |
| 1111 | string::size_type Pos = Host.rfind(':'); |
| 1112 | if (Pos == string::npos || Pos < PortEnd) |
| 1113 | return; |
| 1114 | |
| 1115 | Port = atoi(string(Host,Pos+1).c_str()); |
| 1116 | Host.assign(Host,0,Pos); |
| 1117 | } |
| 1118 | /*}}}*/ |
| 1119 | // URI::operator string - Convert the URI to a string /*{{{*/ |
| 1120 | // --------------------------------------------------------------------- |
| 1121 | /* */ |
| 1122 | URI::operator string() |
| 1123 | { |
| 1124 | string Res; |
| 1125 | |
| 1126 | if (Access.empty() == false) |
| 1127 | Res = Access + ':'; |
| 1128 | |
| 1129 | if (Host.empty() == false) |
| 1130 | { |
| 1131 | if (Access.empty() == false) |
| 1132 | Res += "//"; |
| 1133 | |
| 1134 | if (User.empty() == false) |
| 1135 | { |
| 1136 | Res += User; |
| 1137 | if (Password.empty() == false) |
| 1138 | Res += ":" + Password; |
| 1139 | Res += "@"; |
| 1140 | } |
| 1141 | |
| 1142 | // Add RFC 2732 escaping characters |
| 1143 | if (Access.empty() == false && |
| 1144 | (Host.find('/') != string::npos || Host.find(':') != string::npos)) |
| 1145 | Res += '[' + Host + ']'; |
| 1146 | else |
| 1147 | Res += Host; |
| 1148 | |
| 1149 | if (Port != 0) |
| 1150 | { |
| 1151 | char S[30]; |
| 1152 | sprintf(S,":%u",Port); |
| 1153 | Res += S; |
| 1154 | } |
| 1155 | } |
| 1156 | |
| 1157 | if (Path.empty() == false) |
| 1158 | { |
| 1159 | if (Path[0] != '/') |
| 1160 | Res += "/" + Path; |
| 1161 | else |
| 1162 | Res += Path; |
| 1163 | } |
| 1164 | |
| 1165 | return Res; |
| 1166 | } |
| 1167 | /*}}}*/ |
| 1168 | // URI::SiteOnly - Return the schema and site for the URI /*{{{*/ |
| 1169 | // --------------------------------------------------------------------- |
| 1170 | /* */ |
| 1171 | string URI::SiteOnly(const string &URI) |
| 1172 | { |
| 1173 | ::URI U(URI); |
| 1174 | U.User.clear(); |
| 1175 | U.Password.clear(); |
| 1176 | U.Path.clear(); |
| 1177 | U.Port = 0; |
| 1178 | return U; |
| 1179 | } |
| 1180 | /*}}}*/ |