• Skip to content
  • Skip to link menu
Trinity API Reference
  • Trinity API Reference
  • tdeioslave/http
 

tdeioslave/http

  • tdeioslave
  • http
  • kcookiejar
kcookiejar.cpp
1/* This file is part of the KDE File Manager
2
3 Copyright (C) 1998-2000 Waldo Bastian (bastian@kde.org)
4 Copyright (C) 2000,2001 Dawit Alemayehu (adawit@kde.org)
5
6 Permission is hereby granted, free of charge, to any person obtaining a copy
7 of this software and associated documentation files (the "Software"), to deal
8 in the Software without restriction, including without limitation the rights
9 to use, copy, modify, merge, publish, distribute, and/or sell copies of the
10 Software, and to permit persons to whom the Software is furnished to do so,
11 subject to the following conditions:
12
13 The above copyright notice and this permission notice shall be included in
14 all copies or substantial portions of the Software.
15
16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
20 ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22*/
23//----------------------------------------------------------------------------
24//
25// KDE File Manager -- HTTP Cookies
26// $Id$
27
28//
29// The cookie protocol is a mess. RFC2109 is a joke since nobody seems to
30// use it. Apart from that it is badly written.
31// We try to implement Netscape Cookies and try to behave us according to
32// RFC2109 as much as we can.
33//
34// We assume cookies do not contain any spaces (Netscape spec.)
35// According to RFC2109 this is allowed though.
36//
37
38#include <config.h>
39#include <sys/types.h>
40#include <sys/stat.h>
41#ifdef HAVE_SYS_PARAM_H
42#include <sys/param.h>
43#endif
44#include <fcntl.h>
45#include <unistd.h>
46#include <stdio.h>
47#include <string.h>
48
49#ifdef USE_SOLARIS
50#include <strings.h>
51#endif
52
53#include <stdlib.h>
54
55//#include <netinet/in.h>
56//#include <arpa/inet.h>
57
58#include <tqstring.h>
59#include <tqstrlist.h>
60#include <tqptrlist.h>
61#include <tqptrdict.h>
62#include <tqfile.h>
63#include <tqdir.h>
64#include <tqregexp.h>
65
66#include <kurl.h>
67#include <krfcdate.h>
68#include <tdeconfig.h>
69#include <ksavefile.h>
70#include <kdebug.h>
71
72#include "kcookiejar.h"
73
74
75// BR87227
76// Waba: Should the number of cookies be limited?
77// I am not convinced of the need of such limit
78// Mozilla seems to limit to 20 cookies / domain
79// but it is unclear which policy it uses to expire
80// cookies when it exceeds that amount
81#undef MAX_COOKIE_LIMIT
82
83#define MAX_COOKIES_PER_HOST 25
84#define READ_BUFFER_SIZE 8192
85#define IP_ADDRESS_EXPRESSION "(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)"
86
87// Note with respect to TQString::fromLatin1( )
88// Cookies are stored as 8 bit data and passed to tdeio_http as
89// latin1 regardless of their actual encoding.
90
91// L1 is used to indicate latin1 constants
92#define L1(x) TQString::fromLatin1(x)
93
94template class TQPtrList<KHttpCookie>;
95template class TQPtrDict<KHttpCookieList>;
96
97TQString KCookieJar::adviceToStr(KCookieAdvice _advice)
98{
99 switch( _advice )
100 {
101 case KCookieAccept: return L1("Accept");
102 case KCookieReject: return L1("Reject");
103 case KCookieAsk: return L1("Ask");
104 default: return L1("Dunno");
105 }
106}
107
108KCookieAdvice KCookieJar::strToAdvice(const TQString &_str)
109{
110 if (_str.isEmpty())
111 return KCookieDunno;
112
113 TQCString advice = _str.lower().latin1();
114
115 if (advice == "accept")
116 return KCookieAccept;
117 else if (advice == "reject")
118 return KCookieReject;
119 else if (advice == "ask")
120 return KCookieAsk;
121
122 return KCookieDunno;
123}
124
125// KHttpCookie
127
128//
129// Cookie constructor
130//
131KHttpCookie::KHttpCookie(const TQString &_host,
132 const TQString &_domain,
133 const TQString &_path,
134 const TQString &_name,
135 const TQString &_value,
136 time_t _expireDate,
137 int _protocolVersion,
138 bool _secure,
139 bool _httpOnly,
140 bool _explicitPath) :
141 mHost(_host),
142 mDomain(_domain),
143 mPath(_path.isEmpty() ? TQString::null : _path),
144 mName(_name),
145 mValue(_value),
146 mExpireDate(_expireDate),
147 mProtocolVersion(_protocolVersion),
148 mSecure(_secure),
149 mCrossDomain(false),
150 mHttpOnly(_httpOnly),
151 mExplicitPath(_explicitPath)
152{
153}
154
155//
156// Checks if a cookie has been expired
157//
158bool KHttpCookie::isExpired(time_t currentDate)
159{
160 return (mExpireDate != 0) && (mExpireDate < currentDate);
161}
162
163//
164// Returns a string for a HTTP-header
165//
166TQString KHttpCookie::cookieStr(bool useDOMFormat)
167{
168 TQString result;
169
170 if (useDOMFormat || (mProtocolVersion == 0))
171 {
172 if ( !mName.isEmpty() )
173 result = mName + '=';
174 result += mValue;
175 }
176 else
177 {
178 result = mName + '=' + mValue;
179 if (mExplicitPath)
180 result += L1("; $Path=\"") + mPath + L1("\"");
181 if (!mDomain.isEmpty())
182 result += L1("; $Domain=\"") + mDomain + L1("\"");
183 }
184 return result;
185}
186
187//
188// Returns whether this cookie should be send to this location.
189bool KHttpCookie::match(const TQString &fqdn, const TQStringList &domains,
190 const TQString &path)
191{
192 // Cookie domain match check
193 if (mDomain.isEmpty())
194 {
195 if (fqdn != mHost)
196 return false;
197 }
198 else if (!domains.contains(mDomain))
199 {
200 if (mDomain[0] == '.')
201 return false;
202
203 // Maybe the domain needs an extra dot.
204 TQString domain = '.' + mDomain;
205 if ( !domains.contains( domain ) )
206 if ( fqdn != mDomain )
207 return false;
208 }
209
210 // Cookie path match check
211 if (mPath.isEmpty())
212 return true;
213
214 // According to the netscape spec both http://www.acme.com/foobar,
215 // http://www.acme.com/foo.bar and http://www.acme.com/foo/bar
216 // match http://www.acme.com/foo.
217 // We only match http://www.acme.com/foo/bar
218
219 if( path.startsWith(mPath) &&
220 (
221 (path.length() == mPath.length() ) || // Paths are exact match
222 (path[mPath.length()-1] == '/') || // mPath ended with a slash
223 (path[mPath.length()] == '/') // A slash follows.
224 ))
225 return true; // Path of URL starts with cookie-path
226
227 return false;
228}
229
230// KHttpCookieList
232
233int KHttpCookieList::compareItems( void * item1, void * item2)
234{
235 int pathLen1 = ((KHttpCookie *)item1)->path().length();
236 int pathLen2 = ((KHttpCookie *)item2)->path().length();
237 if (pathLen1 > pathLen2)
238 return -1;
239 if (pathLen1 < pathLen2)
240 return 1;
241 return 0;
242}
243
244
245// KCookieJar
247
248//
249// Constructs a new cookie jar
250//
251// One jar should be enough for all cookies.
252//
253KCookieJar::KCookieJar()
254{
255 m_cookieDomains.setAutoDelete( true );
256 m_globalAdvice = KCookieDunno;
257 m_configChanged = false;
258 m_cookiesChanged = false;
259
260 TDEConfig cfg("tdehtml/domain_info", true, false, "data");
261 TQStringList countries = cfg.readListEntry("twoLevelTLD");
262 for(TQStringList::ConstIterator it = countries.begin();
263 it != countries.end(); ++it)
264 {
265 m_twoLevelTLD.replace(*it, (int *) 1);
266 }
267}
268
269//
270// Destructs the cookie jar
271//
272// Poor little cookies, they will all be eaten by the cookie monster!
273//
274KCookieJar::~KCookieJar()
275{
276 // Not much to do here
277}
278
279static void removeDuplicateFromList(KHttpCookieList *list, KHttpCookie *cookiePtr, bool nameMatchOnly=false, bool updateWindowId=false)
280{
281 TQString domain1 = cookiePtr->domain();
282 if (domain1.isEmpty())
283 domain1 = cookiePtr->host();
284
285 for ( KHttpCookiePtr cookie=list->first(); cookie != 0; )
286 {
287 TQString domain2 = cookie->domain();
288 if (domain2.isEmpty())
289 domain2 = cookie->host();
290
291 if (
292 (cookiePtr->name() == cookie->name()) &&
293 (
294 nameMatchOnly ||
295 ( (domain1 == domain2) && (cookiePtr->path() == cookie->path()) )
296 )
297 )
298 {
299 if (updateWindowId)
300 {
301 for(TQValueList<long>::ConstIterator it = cookie->windowIds().begin();
302 it != cookie->windowIds().end(); ++it)
303 {
304 long windowId = *it;
305 if (windowId && (cookiePtr->windowIds().find(windowId) == cookiePtr->windowIds().end()))
306 {
307 cookiePtr->windowIds().append(windowId);
308 }
309 }
310 }
311 KHttpCookiePtr old_cookie = cookie;
312 cookie = list->next();
313 list->removeRef( old_cookie );
314 break;
315 }
316 else
317 {
318 cookie = list->next();
319 }
320 }
321}
322
323
324//
325// Looks for cookies in the cookie jar which are appropriate for _url.
326// Returned is a string containing all appropriate cookies in a format
327// which can be added to a HTTP-header without any additional processing.
328//
329TQString KCookieJar::findCookies(const TQString &_url, bool useDOMFormat, long windowId, KHttpCookieList *pendingCookies)
330{
331 TQString cookieStr;
332 TQStringList domains;
333 TQString fqdn;
334 TQString path;
335 KHttpCookiePtr cookie;
336 KCookieAdvice advice = m_globalAdvice;
337
338 if (!parseURL(_url, fqdn, path))
339 return cookieStr;
340
341 bool secureRequest = (_url.find( L1("https://"), 0, false) == 0 ||
342 _url.find( L1("webdavs://"), 0, false) == 0);
343
344 // kdDebug(7104) << "findCookies: URL= " << _url << ", secure = " << secureRequest << endl;
345
346 extractDomains(fqdn, domains);
347
348 KHttpCookieList allCookies;
349
350 for(TQStringList::ConstIterator it = domains.begin();
351 true;
352 ++it)
353 {
354 KHttpCookieList *cookieList;
355 if (it == domains.end())
356 {
357 cookieList = pendingCookies; // Add pending cookies
358 pendingCookies = 0;
359 if (!cookieList)
360 break;
361 }
362 else
363 {
364 TQString key = (*it).isNull() ? L1("") : (*it);
365 cookieList = m_cookieDomains[key];
366 if (!cookieList)
367 continue; // No cookies for this domain
368 }
369
370 if (cookieList->getAdvice() != KCookieDunno)
371 advice = cookieList->getAdvice();
372
373 for ( cookie=cookieList->first(); cookie != 0; cookie=cookieList->next() )
374 {
375 // If the we are setup to automatically accept all session cookies and to
376 // treat all cookies as session cookies or the current cookie is a session
377 // cookie, then send the cookie back regardless of either policy.
378 if (advice == KCookieReject &&
379 !(m_autoAcceptSessionCookies &&
380 (m_ignoreCookieExpirationDate || cookie->expireDate() == 0)))
381 continue;
382
383 if (!cookie->match(fqdn, domains, path))
384 continue;
385
386 if( cookie->isSecure() && !secureRequest )
387 continue;
388
389 if( cookie->isHttpOnly() && useDOMFormat )
390 continue;
391
392 // Do not send expired cookies.
393 if ( cookie->isExpired (time(0)) )
394 {
395 // Note there is no need to actually delete the cookie here
396 // since the cookieserver will invoke ::saveCookieJar because
397 // of the state change below. This will then do the job of
398 // deleting the cookie for us.
399 m_cookiesChanged = true;
400 continue;
401 }
402
403 if (windowId && (cookie->windowIds().find(windowId) == cookie->windowIds().end()))
404 {
405 cookie->windowIds().append(windowId);
406 }
407
408 if (it == domains.end()) // Only needed when processing pending cookies
409 removeDuplicateFromList(&allCookies, cookie);
410
411 allCookies.append(cookie);
412 }
413 if (it == domains.end())
414 break; // Finished.
415 }
416
417 int cookieCount = 0;
418
419 int protVersion=0;
420 for ( cookie=allCookies.first(); cookie != 0; cookie=allCookies.next() )
421 {
422 if (cookie->protocolVersion() > protVersion)
423 protVersion = cookie->protocolVersion();
424 }
425
426 for ( cookie=allCookies.first(); cookie != 0; cookie=allCookies.next() )
427 {
428 if (useDOMFormat)
429 {
430 if (cookieCount > 0)
431 cookieStr += L1("; ");
432 cookieStr += cookie->cookieStr(true);
433 }
434 else
435 {
436 if (cookieCount == 0)
437 {
438 cookieStr += L1("Cookie: ");
439 if (protVersion > 0)
440 {
441 TQString version;
442 version.sprintf("$Version=%d; ", protVersion); // Without quotes
443 cookieStr += version;
444 }
445 }
446 else
447 {
448 cookieStr += L1("; ");
449 }
450 cookieStr += cookie->cookieStr(false);
451 }
452 cookieCount++;
453 }
454
455 return cookieStr;
456}
457
458//
459// This function parses a string like 'my_name="my_value";' and returns
460// 'my_name' in Name and 'my_value' in Value.
461//
462// A pointer to the end of the parsed part is returned.
463// This pointer points either to:
464// '\0' - The end of the string has reached.
465// ';' - Another my_name="my_value" pair follows
466// ',' - Another cookie follows
467// '\n' - Another header follows
468static const char * parseNameValue(const char *header,
469 TQString &Name,
470 TQString &Value,
471 bool keepQuotes=false,
472 bool rfcQuotes=false)
473{
474 const char *s = header;
475 // Parse 'my_name' part
476 for(; (*s != '='); s++)
477 {
478 if ((*s=='\0') || (*s==';') || (*s=='\n'))
479 {
480 // No '=' sign -> use string as the value, name is empty
481 // (behavior found in Mozilla and IE)
482 Name = "";
483 Value = TQString::fromLatin1(header);
484 Value.truncate( s - header );
485 Value = Value.stripWhiteSpace();
486 return (s);
487 }
488 }
489
490 Name = header;
491 Name.truncate( s - header );
492 Name = Name.stripWhiteSpace();
493
494 // *s == '='
495 s++;
496
497 // Skip any whitespace
498 for(; (*s == ' ') || (*s == '\t'); s++)
499 {
500 if ((*s=='\0') || (*s==';') || (*s=='\n'))
501 {
502 // End of Name
503 Value = "";
504 return (s);
505 }
506 }
507
508 if ((rfcQuotes || !keepQuotes) && (*s == '\"'))
509 {
510 // Parse '"my_value"' part (quoted value)
511 if (keepQuotes)
512 header = s++;
513 else
514 header = ++s; // skip "
515 for(;(*s != '\"');s++)
516 {
517 if ((*s=='\0') || (*s=='\n'))
518 {
519 // End of Name
520 Value = TQString::fromLatin1(header);
521 Value.truncate(s - header);
522 return (s);
523 }
524 }
525 Value = TQString::fromLatin1(header);
526 // *s == '\"';
527 if (keepQuotes)
528 Value.truncate( ++s - header );
529 else
530 Value.truncate( s++ - header );
531
532 // Skip any remaining garbage
533 for(;; s++)
534 {
535 if ((*s=='\0') || (*s==';') || (*s=='\n'))
536 break;
537 }
538 }
539 else
540 {
541 // Parse 'my_value' part (unquoted value)
542 header = s;
543 while ((*s != '\0') && (*s != ';') && (*s != '\n'))
544 s++;
545 // End of Name
546 Value = TQString::fromLatin1(header);
547 Value.truncate( s - header );
548 Value = Value.stripWhiteSpace();
549 }
550 return (s);
551
552}
553
554void KCookieJar::stripDomain(const TQString &_fqdn, TQString &_domain)
555{
556 TQStringList domains;
557 extractDomains(_fqdn, domains);
558 if (domains.count() > 3)
559 _domain = domains[3];
560 else
561 _domain = domains[0];
562}
563
564TQString KCookieJar::stripDomain( KHttpCookiePtr cookiePtr)
565{
566 TQString domain; // We file the cookie under this domain.
567 if (cookiePtr->domain().isEmpty())
568 stripDomain( cookiePtr->host(), domain);
569 else
570 stripDomain (cookiePtr->domain(), domain);
571 return domain;
572}
573
574bool KCookieJar::parseURL(const TQString &_url,
575 TQString &_fqdn,
576 TQString &_path)
577{
578 KURL kurl(_url);
579 if (!kurl.isValid())
580 return false;
581
582 _fqdn = kurl.host().lower();
583 if (kurl.port())
584 {
585 if (((kurl.protocol() == L1("http")) && (kurl.port() != 80)) ||
586 ((kurl.protocol() == L1("https")) && (kurl.port() != 443)))
587 {
588 _fqdn = L1("%1:%2").arg(kurl.port()).arg(_fqdn);
589 }
590 }
591
592 // Cookie spoofing protection. Since there is no way a path separator
593 // or escape encoded character is allowed in the hostname according
594 // to RFC 2396, reject attempts to include such things there!
595 if(_fqdn.find('/') > -1 || _fqdn.find('%') > -1)
596 {
597 return false; // deny everything!!
598 }
599
600 _path = kurl.path();
601 if (_path.isEmpty())
602 _path = L1("/");
603
604 TQRegExp exp(L1("[\\\\/]\\.\\.[\\\\/]"));
605 // Weird path, cookie stealing attempt?
606 if (exp.search(_path) != -1)
607 return false; // Deny everything!!
608
609 return true;
610}
611
612void KCookieJar::extractDomains(const TQString &_fqdn,
613 TQStringList &_domains)
614{
615 // Return numeric IPv6 addresses as is...
616 if (_fqdn[0] == '[')
617 {
618 _domains.append( _fqdn );
619 return;
620 }
621 // Return numeric IPv4 addresses as is...
622 if ((_fqdn.at(0) >= TQChar('0')) && (_fqdn.at(0) <= TQChar('9')))
623 {
624 if (_fqdn.find(TQRegExp(IP_ADDRESS_EXPRESSION)) > -1)
625 {
626 _domains.append( _fqdn );
627 return;
628 }
629 }
630
631 TQStringList partList = TQStringList::split('.', _fqdn, false);
632
633 if (partList.count())
634 partList.remove(partList.begin()); // Remove hostname
635
636 while(partList.count())
637 {
638
639 if (partList.count() == 1)
640 break; // We only have a TLD left.
641
642 if ((partList.count() == 2) && (m_twoLevelTLD[partList[1].lower()]))
643 {
644 // This domain uses two-level TLDs in the form xxxx.yy
645 break;
646 }
647
648 if ((partList.count() == 2) && (partList[1].length() == 2))
649 {
650 // If this is a TLD, we should stop. (e.g. co.uk)
651 // We assume this is a TLD if it ends with .xx.yy or .x.yy
652 if (partList[0].length() <= 2)
653 break; // This is a TLD.
654
655 // Catch some TLDs that we miss with the previous check
656 // e.g. com.au, org.uk, mil.co
657 TQCString t = partList[0].lower().utf8();
658 if ((t == "com") || (t == "net") || (t == "org") || (t == "gov") || (t == "edu") || (t == "mil") || (t == "int"))
659 break;
660 }
661
662 TQString domain = partList.join(L1("."));
663 _domains.append(domain);
664 _domains.append('.' + domain);
665 partList.remove(partList.begin()); // Remove part
666 }
667
668 // Always add the FQDN at the start of the list for
669 // hostname == cookie-domainname checks!
670 _domains.prepend( '.' + _fqdn );
671 _domains.prepend( _fqdn );
672}
673
674
675/*
676 Changes dates in from the following format
677
678 Wed Sep 12 07:00:00 2007 GMT
679 to
680 Wed Sep 12 2007 07:00:00 GMT
681
682 to allow KRFCDate::parseDate to properly parse expiration date formats
683 used in cookies by some servers such as amazon.com. See BR# 145244.
684*/
685static TQString fixupDateTime(const TQString& dt)
686{
687 const int index = dt.find(TQRegExp("[0-9]{1,2}:[0-9]{1,2}:[0-9]{1,2}"));
688
689 if (index > -1)
690 {
691 TQStringList dateStrList = TQStringList::split(' ', dt.mid(index));
692 if (dateStrList.count() > 1)
693 {
694 TQString date = dateStrList[0];
695 dateStrList[0] = dateStrList[1];
696 dateStrList[1] = date;
697 date = dt;
698 return date.replace(index, date.length(), dateStrList.join(" "));
699 }
700 }
701
702 return dt;
703}
704
705//
706// This function parses cookie_headers and returns a linked list of
707// KHttpCookie objects for all cookies found in cookie_headers.
708// If no cookies could be found 0 is returned.
709//
710// cookie_headers should be a concatenation of all lines of a HTTP-header
711// which start with "Set-Cookie". The lines should be separated by '\n's.
712//
713KHttpCookieList KCookieJar::makeCookies(const TQString &_url,
714 const TQCString &cookie_headers,
715 long windowId)
716{
717 KHttpCookieList cookieList;
718 KHttpCookieList cookieList2;
719 KHttpCookiePtr lastCookie = 0;
720 const char *cookieStr = cookie_headers.data();
721 TQString Name;
722 TQString Value;
723 TQString fqdn;
724 TQString path;
725 bool crossDomain = false;
726
727 if (!parseURL(_url, fqdn, path))
728 {
729 // Error parsing _url
730 return KHttpCookieList();
731 }
732 TQString defaultPath;
733 int i = path.findRev('/');
734 if (i > 0)
735 defaultPath = path.left(i);
736
737 // The hard stuff :)
738 for(;;)
739 {
740 // check for "Set-Cookie"
741 if (strncmp(cookieStr, "Cross-Domain\n", 13) == 0)
742 {
743 cookieStr += 13;
744 crossDomain = true;
745 }
746 else if (strncasecmp(cookieStr, "Set-Cookie:", 11) == 0)
747 {
748 cookieStr = parseNameValue(cookieStr+11, Name, Value, true);
749
750 // Host = FQDN
751 // Default domain = ""
752 // Default path according to rfc2109
753
754 KHttpCookie *cookie = new KHttpCookie(fqdn, L1(""), defaultPath, Name, Value);
755 if (windowId)
756 cookie->mWindowIds.append(windowId);
757 cookie->mCrossDomain = crossDomain;
758
759 // Insert cookie in chain
760 cookieList.append(cookie);
761 lastCookie = cookie;
762 }
763 else if (strncasecmp(cookieStr, "Set-Cookie2:", 12) == 0)
764 {
765 // Attempt to follow rfc2965
766 cookieStr = parseNameValue(cookieStr+12, Name, Value, true, true);
767
768 // Host = FQDN
769 // Default domain = ""
770 // Default path according to rfc2965
771
772 KHttpCookie *cookie = new KHttpCookie(fqdn, L1(""), defaultPath, Name, Value);
773 if (windowId)
774 cookie->mWindowIds.append(windowId);
775 cookie->mCrossDomain = crossDomain;
776
777 // Insert cookie in chain
778 cookieList2.append(cookie);
779 lastCookie = cookie;
780 }
781 else
782 {
783 // This is not the start of a cookie header, skip till next line.
784 while (*cookieStr && *cookieStr != '\n')
785 cookieStr++;
786
787 if (*cookieStr == '\n')
788 cookieStr++;
789
790 if (!*cookieStr)
791 break; // End of cookie_headers
792 else
793 continue; // end of this header, continue with next.
794 }
795
796 while ((*cookieStr == ';') || (*cookieStr == ' '))
797 {
798 cookieStr++;
799
800 // Name-Value pair follows
801 cookieStr = parseNameValue(cookieStr, Name, Value);
802
803 TQCString cName = Name.lower().latin1();
804 if (cName == "domain")
805 {
806 TQString dom = Value.lower();
807 // RFC2965 3.2.2: If an explicitly specified value does not
808 // start with a dot, the user agent supplies a leading dot
809 if(dom.length() && dom[0] != '.')
810 dom.prepend(".");
811 // remove a trailing dot
812 if(dom.length() > 2 && dom[dom.length()-1] == '.')
813 dom = dom.left(dom.length()-1);
814
815 if(dom.contains('.') > 1 || dom == ".local")
816 lastCookie->mDomain = dom;
817 }
818 else if (cName == "max-age")
819 {
820 int max_age = Value.toInt();
821 if (max_age == 0)
822 lastCookie->mExpireDate = 1;
823 else
824 lastCookie->mExpireDate = time(0)+max_age;
825 }
826 else if (cName == "expires")
827 {
828 // Parse brain-dead netscape cookie-format
829 lastCookie->mExpireDate = KRFCDate::parseDate(Value);
830
831 // Workaround for servers that send the expiration date in
832 // 'Wed Sep 12 07:00:00 2007 GMT' format. See BR# 145244.
833 if (lastCookie->mExpireDate == 0)
834 lastCookie->mExpireDate = KRFCDate::parseDate(fixupDateTime(Value));
835 }
836 else if (cName == "path")
837 {
838 if (Value.isEmpty())
839 lastCookie->mPath = TQString::null; // Catch "" <> TQString::null
840 else
841 lastCookie->mPath = KURL::decode_string(Value);
842 lastCookie->mExplicitPath = true;
843 }
844 else if (cName == "version")
845 {
846 lastCookie->mProtocolVersion = Value.toInt();
847 }
848 else if ((cName == "secure") ||
849 (cName.isEmpty() && Value.lower() == L1("secure")))
850 {
851 lastCookie->mSecure = true;
852 }
853 else if ((cName == "httponly") ||
854 (cName.isEmpty() && Value.lower() == L1("httponly")))
855 {
856 lastCookie->mHttpOnly = true;
857 }
858 }
859
860 if (*cookieStr == '\0')
861 break; // End of header
862
863 // Skip ';' or '\n'
864 cookieStr++;
865 }
866
867 // RFC2965 cookies come last so that they override netscape cookies.
868 while( !cookieList2.isEmpty() && (lastCookie = cookieList2.take(0)) )
869 {
870 removeDuplicateFromList(&cookieList, lastCookie, true);
871 cookieList.append(lastCookie);
872 }
873
874 return cookieList;
875}
876
883KHttpCookieList KCookieJar::makeDOMCookies(const TQString &_url,
884 const TQCString &cookie_domstring,
885 long windowId)
886{
887 // A lot copied from above
888 KHttpCookieList cookieList;
889 KHttpCookiePtr lastCookie = 0;
890
891 const char *cookieStr = cookie_domstring.data();
892 TQString Name;
893 TQString Value;
894 TQString fqdn;
895 TQString path;
896
897 if (!parseURL(_url, fqdn, path))
898 {
899 // Error parsing _url
900 return KHttpCookieList();
901 }
902
903 // This time it's easy
904 while(*cookieStr)
905 {
906 cookieStr = parseNameValue(cookieStr, Name, Value);
907
908 // Host = FQDN
909 // Default domain = ""
910 // Default path = ""
911 KHttpCookie *cookie = new KHttpCookie(fqdn, TQString::null, TQString::null,
912 Name, Value );
913 if (windowId)
914 cookie->mWindowIds.append(windowId);
915
916 cookieList.append(cookie);
917 lastCookie = cookie;
918
919 if (*cookieStr != '\0')
920 cookieStr++; // Skip ';' or '\n'
921 }
922
923 return cookieList;
924}
925
926#ifdef MAX_COOKIE_LIMIT
927static void makeRoom(KHttpCookieList *cookieList, KHttpCookiePtr &cookiePtr)
928{
929 // Too much cookies: throw one away, try to be somewhat clever
930 KHttpCookiePtr lastCookie = 0;
931 for(KHttpCookiePtr cookie = cookieList->first(); cookie; cookie = cookieList->next())
932 {
933 if (cookieList->compareItems(cookie, cookiePtr) < 0)
934 break;
935 lastCookie = cookie;
936 }
937 if (!lastCookie)
938 lastCookie = cookieList->first();
939 cookieList->removeRef(lastCookie);
940}
941#endif
942
943//
944// This function hands a KHttpCookie object over to the cookie jar.
945//
946// On return cookiePtr is set to 0.
947//
948void KCookieJar::addCookie(KHttpCookiePtr &cookiePtr)
949{
950 TQStringList domains;
951 KHttpCookieList *cookieList = 0L;
952
953 // We always need to do this to make sure that the
954 // that cookies of type hostname == cookie-domainname
955 // are properly removed and/or updated as necessary!
956 extractDomains( cookiePtr->host(), domains );
957 for ( TQStringList::ConstIterator it = domains.begin();
958 (it != domains.end() && !cookieList);
959 ++it )
960 {
961 TQString key = (*it).isNull() ? L1("") : (*it);
962 KHttpCookieList *list= m_cookieDomains[key];
963 if ( !list ) continue;
964
965 removeDuplicateFromList(list, cookiePtr, false, true);
966 }
967
968 TQString domain = stripDomain( cookiePtr );
969 TQString key = domain.isNull() ? L1("") : domain;
970 cookieList = m_cookieDomains[ key ];
971 if (!cookieList)
972 {
973 // Make a new cookie list
974 cookieList = new KHttpCookieList();
975 cookieList->setAutoDelete(true);
976
977 // All cookies whose domain is not already
978 // known to us should be added with KCookieDunno.
979 // KCookieDunno means that we use the global policy.
980 cookieList->setAdvice( KCookieDunno );
981
982 m_cookieDomains.insert( domain, cookieList);
983
984 // Update the list of domains
985 m_domainList.append(domain);
986 }
987
988 // Add the cookie to the cookie list
989 // The cookie list is sorted 'longest path first'
990 if (!cookiePtr->isExpired(time(0)))
991 {
992#ifdef MAX_COOKIE_LIMIT
993 if (cookieList->count() >= MAX_COOKIES_PER_HOST)
994 makeRoom(cookieList, cookiePtr); // Delete a cookie
995#endif
996 cookieList->inSort( cookiePtr );
997 m_cookiesChanged = true;
998 }
999 else
1000 {
1001 delete cookiePtr;
1002 }
1003 cookiePtr = 0;
1004}
1005
1006//
1007// This function advices whether a single KHttpCookie object should
1008// be added to the cookie jar.
1009//
1010KCookieAdvice KCookieJar::cookieAdvice(KHttpCookiePtr cookiePtr)
1011{
1012 if (m_rejectCrossDomainCookies && cookiePtr->isCrossDomain())
1013 return KCookieReject;
1014
1015 TQStringList domains;
1016
1017 extractDomains(cookiePtr->host(), domains);
1018
1019 // If the cookie specifies a domain, check whether it is valid. Otherwise,
1020 // accept the cookie anyways but remove the domain="" value to prevent
1021 // cross-site cookie injection.
1022 if (!cookiePtr->domain().isEmpty())
1023 {
1024 if (!domains.contains(cookiePtr->domain()) &&
1025 !cookiePtr->domain().endsWith("."+cookiePtr->host()))
1026 cookiePtr->fixDomain(TQString::null);
1027 }
1028
1029 if (m_autoAcceptSessionCookies && (cookiePtr->expireDate() == 0 ||
1030 m_ignoreCookieExpirationDate))
1031 return KCookieAccept;
1032
1033 KCookieAdvice advice = KCookieDunno;
1034 bool isFQDN = true; // First is FQDN
1035 TQStringList::Iterator it = domains.begin(); // Start with FQDN which first in the list.
1036 while( (advice == KCookieDunno) && (it != domains.end()))
1037 {
1038 TQString domain = *it;
1039 // Check if a policy for the FQDN/domain is set.
1040 if ( domain[0] == '.' || isFQDN )
1041 {
1042 isFQDN = false;
1043 KHttpCookieList *cookieList = m_cookieDomains[domain];
1044 if (cookieList)
1045 advice = cookieList->getAdvice();
1046 }
1047 domains.remove(it);
1048 it = domains.begin(); // Continue from begin of remaining list
1049 }
1050
1051 if (advice == KCookieDunno)
1052 advice = m_globalAdvice;
1053
1054 return advice;
1055}
1056
1057//
1058// This function gets the advice for all cookies originating from
1059// _domain.
1060//
1061KCookieAdvice KCookieJar::getDomainAdvice(const TQString &_domain)
1062{
1063 KHttpCookieList *cookieList = m_cookieDomains[_domain];
1064 KCookieAdvice advice;
1065
1066 if (cookieList)
1067 {
1068 advice = cookieList->getAdvice();
1069 }
1070 else
1071 {
1072 advice = KCookieDunno;
1073 }
1074
1075 return advice;
1076}
1077
1078//
1079// This function sets the advice for all cookies originating from
1080// _domain.
1081//
1082void KCookieJar::setDomainAdvice(const TQString &_domain, KCookieAdvice _advice)
1083{
1084 TQString domain(_domain);
1085 KHttpCookieList *cookieList = m_cookieDomains[domain];
1086
1087 if (cookieList)
1088 {
1089 if (cookieList->getAdvice() != _advice)
1090 {
1091 m_configChanged = true;
1092 // domain is already known
1093 cookieList->setAdvice( _advice);
1094 }
1095
1096 if ((cookieList->isEmpty()) &&
1097 (_advice == KCookieDunno))
1098 {
1099 // This deletes cookieList!
1100 m_cookieDomains.remove(domain);
1101 m_domainList.remove(domain);
1102 }
1103 }
1104 else
1105 {
1106 // domain is not yet known
1107 if (_advice != KCookieDunno)
1108 {
1109 // We should create a domain entry
1110 m_configChanged = true;
1111 // Make a new cookie list
1112 cookieList = new KHttpCookieList();
1113 cookieList->setAutoDelete(true);
1114 cookieList->setAdvice( _advice);
1115 m_cookieDomains.insert( domain, cookieList);
1116 // Update the list of domains
1117 m_domainList.append( domain);
1118 }
1119 }
1120}
1121
1122//
1123// This function sets the advice for all cookies originating from
1124// the same domain as _cookie
1125//
1126void KCookieJar::setDomainAdvice(KHttpCookiePtr cookiePtr, KCookieAdvice _advice)
1127{
1128 TQString domain;
1129 stripDomain(cookiePtr->host(), domain); // We file the cookie under this domain.
1130
1131 setDomainAdvice(domain, _advice);
1132}
1133
1134//
1135// This function sets the global advice for cookies
1136//
1137void KCookieJar::setGlobalAdvice(KCookieAdvice _advice)
1138{
1139 if (m_globalAdvice != _advice)
1140 m_configChanged = true;
1141 m_globalAdvice = _advice;
1142}
1143
1144//
1145// Get a list of all domains known to the cookie jar.
1146//
1147const TQStringList& KCookieJar::getDomainList()
1148{
1149 return m_domainList;
1150}
1151
1152//
1153// Get a list of all cookies in the cookie jar originating from _domain.
1154//
1155const KHttpCookieList *KCookieJar::getCookieList(const TQString & _domain,
1156 const TQString & _fqdn )
1157{
1158 TQString domain;
1159
1160 if (_domain.isEmpty())
1161 stripDomain( _fqdn, domain );
1162 else
1163 domain = _domain;
1164
1165 return m_cookieDomains[domain];
1166}
1167
1168//
1169// Eat a cookie out of the jar.
1170// cookiePtr should be one of the cookies returned by getCookieList()
1171//
1172void KCookieJar::eatCookie(KHttpCookiePtr cookiePtr)
1173{
1174 TQString domain = stripDomain(cookiePtr); // We file the cookie under this domain.
1175 KHttpCookieList *cookieList = m_cookieDomains[domain];
1176
1177 if (cookieList)
1178 {
1179 // This deletes cookiePtr!
1180 if (cookieList->removeRef( cookiePtr ))
1181 m_cookiesChanged = true;
1182
1183 if ((cookieList->isEmpty()) &&
1184 (cookieList->getAdvice() == KCookieDunno))
1185 {
1186 // This deletes cookieList!
1187 m_cookieDomains.remove(domain);
1188
1189 m_domainList.remove(domain);
1190 }
1191 }
1192}
1193
1194void KCookieJar::eatCookiesForDomain(const TQString &domain)
1195{
1196 KHttpCookieList *cookieList = m_cookieDomains[domain];
1197 if (!cookieList || cookieList->isEmpty()) return;
1198
1199 cookieList->clear();
1200 if (cookieList->getAdvice() == KCookieDunno)
1201 {
1202 // This deletes cookieList!
1203 m_cookieDomains.remove(domain);
1204 m_domainList.remove(domain);
1205 }
1206 m_cookiesChanged = true;
1207}
1208
1209void KCookieJar::eatSessionCookies( long windowId )
1210{
1211 if (!windowId)
1212 return;
1213
1214 TQStringList::Iterator it=m_domainList.begin();
1215 for ( ; it != m_domainList.end(); ++it )
1216 eatSessionCookies( *it, windowId, false );
1217}
1218
1219void KCookieJar::eatAllCookies()
1220{
1221 for ( TQStringList::Iterator it=m_domainList.begin();
1222 it != m_domainList.end();)
1223 {
1224 TQString domain = *it++;
1225 // This might remove domain from domainList!
1226 eatCookiesForDomain(domain);
1227 }
1228}
1229
1230void KCookieJar::eatSessionCookies( const TQString& fqdn, long windowId,
1231 bool isFQDN )
1232{
1233 KHttpCookieList* cookieList;
1234 if ( !isFQDN )
1235 cookieList = m_cookieDomains[fqdn];
1236 else
1237 {
1238 TQString domain;
1239 stripDomain( fqdn, domain );
1240 cookieList = m_cookieDomains[domain];
1241 }
1242
1243 if ( cookieList )
1244 {
1245 KHttpCookiePtr cookie=cookieList->first();
1246 for (; cookie != 0;)
1247 {
1248 if ((cookie->expireDate() != 0) && !m_ignoreCookieExpirationDate)
1249 {
1250 cookie = cookieList->next();
1251 continue;
1252 }
1253
1254 TQValueList<long> &ids = cookie->windowIds();
1255 if (!ids.remove(windowId) || !ids.isEmpty())
1256 {
1257 cookie = cookieList->next();
1258 continue;
1259 }
1260 KHttpCookiePtr old_cookie = cookie;
1261 cookie = cookieList->next();
1262 cookieList->removeRef( old_cookie );
1263 }
1264 }
1265}
1266
1267//
1268// Saves all cookies to the file '_filename'.
1269// On succes 'true' is returned.
1270// On failure 'false' is returned.
1271bool KCookieJar::saveCookies(const TQString &_filename)
1272{
1273 KSaveFile saveFile(_filename, 0600);
1274
1275 if (saveFile.status() != 0)
1276 return false;
1277
1278 FILE *fStream = saveFile.fstream();
1279
1280 time_t curTime = time(0);
1281
1282 fprintf(fStream, "# KDE Cookie File v2\n#\n");
1283
1284 fprintf(fStream, "%-20s %-20s %-12s %-10s %-4s %-20s %-4s %s\n",
1285 "# Host", "Domain", "Path", "Exp.date", "Prot",
1286 "Name", "Sec", "Value");
1287
1288 for ( TQStringList::Iterator it=m_domainList.begin(); it != m_domainList.end();
1289 it++ )
1290 {
1291 const TQString &domain = *it;
1292 bool domainPrinted = false;
1293
1294 KHttpCookieList *cookieList = m_cookieDomains[domain];
1295 KHttpCookiePtr cookie=cookieList->last();
1296
1297 for (; cookie != 0;)
1298 {
1299 if (cookie->isExpired(curTime))
1300 {
1301 // Delete expired cookies
1302 KHttpCookiePtr old_cookie = cookie;
1303 cookie = cookieList->prev();
1304 cookieList->removeRef( old_cookie );
1305 }
1306 else if (cookie->expireDate() != 0 && !m_ignoreCookieExpirationDate)
1307 {
1308 if (!domainPrinted)
1309 {
1310 domainPrinted = true;
1311 fprintf(fStream, "[%s]\n", domain.local8Bit().data());
1312 }
1313 // Store persistent cookies
1314 TQString path = L1("\"");
1315 path += cookie->path();
1316 path += '"';
1317 TQString domain = L1("\"");
1318 domain += cookie->domain();
1319 domain += '"';
1320 fprintf(fStream, "%-20s %-20s %-12s %10lu %3d %-20s %-4i %s\n",
1321 cookie->host().latin1(), domain.latin1(),
1322 path.latin1(), (unsigned long) cookie->expireDate(),
1323 cookie->protocolVersion(),
1324 cookie->name().isEmpty() ? cookie->value().latin1() : cookie->name().latin1(),
1325 (cookie->isSecure() ? 1 : 0) + (cookie->isHttpOnly() ? 2 : 0) +
1326 (cookie->hasExplicitPath() ? 4 : 0) + (cookie->name().isEmpty() ? 8 : 0),
1327 cookie->value().latin1());
1328 cookie = cookieList->prev();
1329 }
1330 else
1331 {
1332 // Skip session-only cookies
1333 cookie = cookieList->prev();
1334 }
1335 }
1336 }
1337
1338 return saveFile.close();
1339}
1340
1341typedef char *charPtr;
1342
1343static const char *parseField(charPtr &buffer, bool keepQuotes=false)
1344{
1345 char *result;
1346 if (!keepQuotes && (*buffer == '\"'))
1347 {
1348 // Find terminating "
1349 buffer++;
1350 result = buffer;
1351 while((*buffer != '\"') && (*buffer))
1352 buffer++;
1353 }
1354 else
1355 {
1356 // Find first white space
1357 result = buffer;
1358 while((*buffer != ' ') && (*buffer != '\t') && (*buffer != '\n') && (*buffer))
1359 buffer++;
1360 }
1361
1362 if (!*buffer)
1363 return result; //
1364 *buffer++ = '\0';
1365
1366 // Skip white-space
1367 while((*buffer == ' ') || (*buffer == '\t') || (*buffer == '\n'))
1368 buffer++;
1369
1370 return result;
1371}
1372
1373
1374//
1375// Reloads all cookies from the file '_filename'.
1376// On succes 'true' is returned.
1377// On failure 'false' is returned.
1378bool KCookieJar::loadCookies(const TQString &_filename)
1379{
1380 FILE *fStream = fopen( TQFile::encodeName(_filename), "r");
1381 if (fStream == 0)
1382 {
1383 return false;
1384 }
1385
1386 time_t curTime = time(0);
1387
1388 char *buffer = new char[READ_BUFFER_SIZE];
1389
1390 bool err = false;
1391 err = (fgets(buffer, READ_BUFFER_SIZE, fStream) == 0);
1392
1393 int version = 1;
1394 if (!err)
1395 {
1396 if (strcmp(buffer, "# KDE Cookie File\n") == 0)
1397 {
1398 // version 1
1399 }
1400 else if (sscanf(buffer, "# KDE Cookie File v%d\n", &version) != 1)
1401 {
1402 err = true;
1403 }
1404 }
1405
1406 if (!err)
1407 {
1408 while(fgets(buffer, READ_BUFFER_SIZE, fStream) != 0)
1409 {
1410 char *line = buffer;
1411 // Skip lines which begin with '#' or '['
1412 if ((line[0] == '#') || (line[0] == '['))
1413 continue;
1414
1415 const char *host( parseField(line) );
1416 const char *domain( parseField(line) );
1417 const char *path( parseField(line) );
1418 const char *expStr( parseField(line) );
1419 if (!expStr) continue;
1420 int expDate = (time_t) strtoul(expStr, 0, 10);
1421 const char *verStr( parseField(line) );
1422 if (!verStr) continue;
1423 int protVer = (time_t) strtoul(verStr, 0, 10);
1424 const char *name( parseField(line) );
1425 bool keepQuotes = false;
1426 bool secure = false;
1427 bool httpOnly = false;
1428 bool explicitPath = false;
1429 const char *value = 0;
1430 if ((version == 2) || (protVer >= 200))
1431 {
1432 if (protVer >= 200)
1433 protVer -= 200;
1434 int i = atoi( parseField(line) );
1435 secure = i & 1;
1436 httpOnly = i & 2;
1437 explicitPath = i & 4;
1438 if (i & 8)
1439 name = "";
1440 line[strlen(line)-1] = '\0'; // Strip LF.
1441 value = line;
1442 }
1443 else
1444 {
1445 if (protVer >= 100)
1446 {
1447 protVer -= 100;
1448 keepQuotes = true;
1449 }
1450 value = parseField(line, keepQuotes);
1451 secure = atoi( parseField(line) );
1452 }
1453
1454 // Parse error
1455 if (!value) continue;
1456
1457 // Expired or parse error
1458 if ((expDate == 0) || (expDate < curTime))
1459 continue;
1460
1461 KHttpCookie *cookie = new KHttpCookie(TQString::fromLatin1(host),
1462 TQString::fromLatin1(domain),
1463 TQString::fromLatin1(path),
1464 TQString::fromLatin1(name),
1465 TQString::fromLatin1(value),
1466 expDate, protVer,
1467 secure, httpOnly, explicitPath);
1468 addCookie(cookie);
1469 }
1470 }
1471 delete [] buffer;
1472 m_cookiesChanged = false;
1473
1474 fclose( fStream);
1475 return err;
1476}
1477
1478//
1479// Save the cookie configuration
1480//
1481
1482void KCookieJar::saveConfig(TDEConfig *_config)
1483{
1484 if (!m_configChanged)
1485 return;
1486
1487 _config->setGroup("Cookie Dialog");
1488 _config->writeEntry("PreferredPolicy", m_preferredPolicy);
1489 _config->writeEntry("ShowCookieDetails", m_showCookieDetails );
1490 _config->setGroup("Cookie Policy");
1491 _config->writeEntry("CookieGlobalAdvice", adviceToStr( m_globalAdvice));
1492
1493 TQStringList domainSettings;
1494 for ( TQStringList::Iterator it=m_domainList.begin();
1495 it != m_domainList.end();
1496 it++ )
1497 {
1498 const TQString &domain = *it;
1499 KCookieAdvice advice = getDomainAdvice( domain);
1500 if (advice != KCookieDunno)
1501 {
1502 TQString value(domain);
1503 value += ':';
1504 value += adviceToStr(advice);
1505 domainSettings.append(value);
1506 }
1507 }
1508 _config->writeEntry("CookieDomainAdvice", domainSettings);
1509 _config->sync();
1510 m_configChanged = false;
1511}
1512
1513
1514//
1515// Load the cookie configuration
1516//
1517
1518void KCookieJar::loadConfig(TDEConfig *_config, bool reparse )
1519{
1520 if ( reparse )
1521 _config->reparseConfiguration();
1522
1523 _config->setGroup("Cookie Dialog");
1524 m_showCookieDetails = _config->readBoolEntry( "ShowCookieDetails" );
1525 m_preferredPolicy = _config->readNumEntry( "PreferredPolicy", 0 );
1526
1527 _config->setGroup("Cookie Policy");
1528 TQStringList domainSettings = _config->readListEntry("CookieDomainAdvice");
1529 m_rejectCrossDomainCookies = _config->readBoolEntry( "RejectCrossDomainCookies", true );
1530 m_autoAcceptSessionCookies = _config->readBoolEntry( "AcceptSessionCookies", true );
1531 m_ignoreCookieExpirationDate = _config->readBoolEntry( "IgnoreExpirationDate", false );
1532 TQString value = _config->readEntry("CookieGlobalAdvice", L1("Ask"));
1533 m_globalAdvice = strToAdvice(value);
1534
1535 // Reset current domain settings first.
1536 for ( TQStringList::Iterator it=m_domainList.begin(); it != m_domainList.end(); )
1537 {
1538 // Make sure to update iterator before calling setDomainAdvice()
1539 // setDomainAdvice() might delete the domain from domainList.
1540 TQString domain = *it++;
1541 setDomainAdvice(domain, KCookieDunno);
1542 }
1543
1544 // Now apply the domain settings read from config file...
1545 for ( TQStringList::Iterator it=domainSettings.begin();
1546 it != domainSettings.end(); )
1547 {
1548 const TQString &value = *it++;
1549
1550 int sepPos = value.findRev(':');
1551
1552 if (sepPos <= 0)
1553 continue;
1554
1555 TQString domain(value.left(sepPos));
1556 KCookieAdvice advice = strToAdvice( value.mid(sepPos + 1) );
1557 setDomainAdvice(domain, advice);
1558 }
1559}

tdeioslave/http

Skip menu "tdeioslave/http"
  • Main Page
  • Alphabetical List
  • Class List
  • File List

tdeioslave/http

Skip menu "tdeioslave/http"
  • arts
  • dcop
  • dnssd
  • interfaces
  •   kspeech
  •     interface
  •     library
  •   tdetexteditor
  • kate
  • kded
  • kdoctools
  • kimgio
  • kjs
  • libtdemid
  • libtdescreensaver
  • tdeabc
  • tdecmshell
  • tdecore
  • tdefx
  • tdehtml
  • tdeinit
  • tdeio
  •   bookmarks
  •   httpfilter
  •   kpasswdserver
  •   kssl
  •   tdefile
  •   tdeio
  •   tdeioexec
  • tdeioslave
  •   http
  • tdemdi
  •   tdemdi
  • tdenewstuff
  • tdeparts
  • tdeprint
  • tderandr
  • tderesources
  • tdespell2
  • tdesu
  • tdeui
  • tdeunittest
  • tdeutils
  • tdewallet
Generated for tdeioslave/http by doxygen 1.9.4
This website is maintained by Timothy Pearson.