js/jsd/jsd_text.cpp

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

michael@0 1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
michael@0 2 * vim: set ts=8 sts=4 et sw=4 tw=99:
michael@0 3 * This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 /*
michael@0 8 * JavaScript Debugging support - Source Text functions
michael@0 9 */
michael@0 10
michael@0 11 #include <ctype.h>
michael@0 12 #include "jsd.h"
michael@0 13 #include "jsprf.h"
michael@0 14
michael@0 15 #ifdef DEBUG
michael@0 16 void JSD_ASSERT_VALID_SOURCE_TEXT(JSDSourceText* jsdsrc)
michael@0 17 {
michael@0 18 MOZ_ASSERT(jsdsrc);
michael@0 19 MOZ_ASSERT(jsdsrc->url);
michael@0 20 }
michael@0 21 #endif
michael@0 22
michael@0 23 /***************************************************************************/
michael@0 24 /* XXX add notification */
michael@0 25
michael@0 26 static void
michael@0 27 _clearText(JSDContext* jsdc, JSDSourceText* jsdsrc)
michael@0 28 {
michael@0 29 if( jsdsrc->text )
michael@0 30 free(jsdsrc->text);
michael@0 31 jsdsrc->text = nullptr;
michael@0 32 jsdsrc->textLength = 0;
michael@0 33 jsdsrc->textSpace = 0;
michael@0 34 jsdsrc->status = JSD_SOURCE_CLEARED;
michael@0 35 jsdsrc->dirty = true;
michael@0 36 jsdsrc->alterCount = jsdc->sourceAlterCount++ ;
michael@0 37 jsdsrc->doingEval = false;
michael@0 38 }
michael@0 39
michael@0 40 static bool
michael@0 41 _appendText(JSDContext* jsdc, JSDSourceText* jsdsrc,
michael@0 42 const char* text, size_t length)
michael@0 43 {
michael@0 44 #define MEMBUF_GROW 1000
michael@0 45
michael@0 46 unsigned neededSize = jsdsrc->textLength + length;
michael@0 47
michael@0 48 if( neededSize > jsdsrc->textSpace )
michael@0 49 {
michael@0 50 char* newBuf;
michael@0 51 unsigned iNewSize;
michael@0 52
michael@0 53 /* if this is the first alloc, the req might be all that's needed*/
michael@0 54 if( ! jsdsrc->textSpace )
michael@0 55 iNewSize = length;
michael@0 56 else
michael@0 57 iNewSize = (neededSize * 5 / 4) + MEMBUF_GROW;
michael@0 58
michael@0 59 newBuf = (char*) realloc(jsdsrc->text, iNewSize);
michael@0 60 if( ! newBuf )
michael@0 61 {
michael@0 62 /* try again with the minimal size really asked for */
michael@0 63 iNewSize = neededSize;
michael@0 64 newBuf = (char*) realloc(jsdsrc->text, iNewSize);
michael@0 65 if( ! newBuf )
michael@0 66 {
michael@0 67 /* out of memory */
michael@0 68 _clearText( jsdc, jsdsrc );
michael@0 69 jsdsrc->status = JSD_SOURCE_FAILED;
michael@0 70 return false;
michael@0 71 }
michael@0 72 }
michael@0 73
michael@0 74 jsdsrc->text = newBuf;
michael@0 75 jsdsrc->textSpace = iNewSize;
michael@0 76 }
michael@0 77
michael@0 78 memcpy(jsdsrc->text + jsdsrc->textLength, text, length);
michael@0 79 jsdsrc->textLength += length;
michael@0 80 return true;
michael@0 81 }
michael@0 82
michael@0 83 static JSDSourceText*
michael@0 84 _newSource(JSDContext* jsdc, char* url)
michael@0 85 {
michael@0 86 JSDSourceText* jsdsrc = (JSDSourceText*)calloc(1,sizeof(JSDSourceText));
michael@0 87 if( ! jsdsrc )
michael@0 88 return nullptr;
michael@0 89
michael@0 90 jsdsrc->url = url;
michael@0 91 jsdsrc->status = JSD_SOURCE_INITED;
michael@0 92 jsdsrc->dirty = true;
michael@0 93 jsdsrc->alterCount = jsdc->sourceAlterCount++ ;
michael@0 94
michael@0 95 return jsdsrc;
michael@0 96 }
michael@0 97
michael@0 98 static void
michael@0 99 _destroySource(JSDContext* jsdc, JSDSourceText* jsdsrc)
michael@0 100 {
michael@0 101 MOZ_ASSERT(nullptr == jsdsrc->text); /* must _clearText() first */
michael@0 102 free(jsdsrc->url);
michael@0 103 free(jsdsrc);
michael@0 104 }
michael@0 105
michael@0 106 static void
michael@0 107 _removeSource(JSDContext* jsdc, JSDSourceText* jsdsrc)
michael@0 108 {
michael@0 109 JS_REMOVE_LINK(&jsdsrc->links);
michael@0 110 _clearText(jsdc, jsdsrc);
michael@0 111 _destroySource(jsdc, jsdsrc);
michael@0 112 }
michael@0 113
michael@0 114 static JSDSourceText*
michael@0 115 _addSource(JSDContext* jsdc, char* url)
michael@0 116 {
michael@0 117 JSDSourceText* jsdsrc = _newSource(jsdc, url);
michael@0 118 if( ! jsdsrc )
michael@0 119 return nullptr;
michael@0 120 JS_INSERT_LINK(&jsdsrc->links, &jsdc->sources);
michael@0 121 return jsdsrc;
michael@0 122 }
michael@0 123
michael@0 124 static void
michael@0 125 _moveSourceToRemovedList(JSDContext* jsdc, JSDSourceText* jsdsrc)
michael@0 126 {
michael@0 127 _clearText(jsdc, jsdsrc);
michael@0 128 JS_REMOVE_LINK(&jsdsrc->links);
michael@0 129 JS_INSERT_LINK(&jsdsrc->links, &jsdc->removedSources);
michael@0 130 }
michael@0 131
michael@0 132 static void
michael@0 133 _removeSourceFromRemovedList( JSDContext* jsdc, JSDSourceText* jsdsrc )
michael@0 134 {
michael@0 135 JS_REMOVE_LINK(&jsdsrc->links);
michael@0 136 _destroySource( jsdc, jsdsrc );
michael@0 137 }
michael@0 138
michael@0 139 static bool
michael@0 140 _isSourceInSourceList(JSDContext* jsdc, JSDSourceText* jsdsrcToFind)
michael@0 141 {
michael@0 142 JSDSourceText *jsdsrc;
michael@0 143
michael@0 144 for( jsdsrc = (JSDSourceText*)jsdc->sources.next;
michael@0 145 jsdsrc != (JSDSourceText*)&jsdc->sources;
michael@0 146 jsdsrc = (JSDSourceText*)jsdsrc->links.next )
michael@0 147 {
michael@0 148 if( jsdsrc == jsdsrcToFind )
michael@0 149 return true;
michael@0 150 }
michael@0 151 return false;
michael@0 152 }
michael@0 153
michael@0 154 /* compare strings in a case insensitive manner with a length limit
michael@0 155 */
michael@0 156
michael@0 157 static int
michael@0 158 strncasecomp (const char* one, const char * two, int n)
michael@0 159 {
michael@0 160 const char *pA;
michael@0 161 const char *pB;
michael@0 162
michael@0 163 for(pA=one, pB=two;; pA++, pB++)
michael@0 164 {
michael@0 165 int tmp;
michael@0 166 if (pA == one+n)
michael@0 167 return 0;
michael@0 168 if (!(*pA && *pB))
michael@0 169 return *pA - *pB;
michael@0 170 tmp = tolower(*pA) - tolower(*pB);
michael@0 171 if (tmp)
michael@0 172 return tmp;
michael@0 173 }
michael@0 174 }
michael@0 175
michael@0 176 static const char file_url_prefix[] = "file:";
michael@0 177 #define FILE_URL_PREFIX_LEN (sizeof file_url_prefix - 1)
michael@0 178
michael@0 179 char*
michael@0 180 jsd_BuildNormalizedURL( const char* url_string )
michael@0 181 {
michael@0 182 char *new_url_string;
michael@0 183
michael@0 184 if( ! url_string )
michael@0 185 return nullptr;
michael@0 186
michael@0 187 if (!strncasecomp(url_string, file_url_prefix, FILE_URL_PREFIX_LEN) &&
michael@0 188 url_string[FILE_URL_PREFIX_LEN + 0] == '/' &&
michael@0 189 url_string[FILE_URL_PREFIX_LEN + 1] == '/') {
michael@0 190 new_url_string = JS_smprintf("%s%s",
michael@0 191 file_url_prefix,
michael@0 192 url_string + FILE_URL_PREFIX_LEN + 2);
michael@0 193 } else {
michael@0 194 new_url_string = strdup(url_string);
michael@0 195 }
michael@0 196 return new_url_string;
michael@0 197 }
michael@0 198
michael@0 199 /***************************************************************************/
michael@0 200
michael@0 201 void
michael@0 202 jsd_DestroyAllSources( JSDContext* jsdc )
michael@0 203 {
michael@0 204 JSDSourceText *jsdsrc;
michael@0 205 JSDSourceText *next;
michael@0 206
michael@0 207 for( jsdsrc = (JSDSourceText*)jsdc->sources.next;
michael@0 208 jsdsrc != (JSDSourceText*)&jsdc->sources;
michael@0 209 jsdsrc = next )
michael@0 210 {
michael@0 211 next = (JSDSourceText*)jsdsrc->links.next;
michael@0 212 _removeSource( jsdc, jsdsrc );
michael@0 213 }
michael@0 214
michael@0 215 for( jsdsrc = (JSDSourceText*)jsdc->removedSources.next;
michael@0 216 jsdsrc != (JSDSourceText*)&jsdc->removedSources;
michael@0 217 jsdsrc = next )
michael@0 218 {
michael@0 219 next = (JSDSourceText*)jsdsrc->links.next;
michael@0 220 _removeSourceFromRemovedList( jsdc, jsdsrc );
michael@0 221 }
michael@0 222
michael@0 223 }
michael@0 224
michael@0 225 JSDSourceText*
michael@0 226 jsd_IterateSources(JSDContext* jsdc, JSDSourceText **iterp)
michael@0 227 {
michael@0 228 JSDSourceText *jsdsrc = *iterp;
michael@0 229
michael@0 230 if( !jsdsrc )
michael@0 231 jsdsrc = (JSDSourceText *)jsdc->sources.next;
michael@0 232 if( jsdsrc == (JSDSourceText *)&jsdc->sources )
michael@0 233 return nullptr;
michael@0 234 *iterp = (JSDSourceText *)jsdsrc->links.next;
michael@0 235 return jsdsrc;
michael@0 236 }
michael@0 237
michael@0 238 JSDSourceText*
michael@0 239 jsd_FindSourceForURL(JSDContext* jsdc, const char* url)
michael@0 240 {
michael@0 241 JSDSourceText *jsdsrc;
michael@0 242
michael@0 243 for( jsdsrc = (JSDSourceText *)jsdc->sources.next;
michael@0 244 jsdsrc != (JSDSourceText *)&jsdc->sources;
michael@0 245 jsdsrc = (JSDSourceText *)jsdsrc->links.next )
michael@0 246 {
michael@0 247 if( 0 == strcmp(jsdsrc->url, url) )
michael@0 248 return jsdsrc;
michael@0 249 }
michael@0 250 return nullptr;
michael@0 251 }
michael@0 252
michael@0 253 const char*
michael@0 254 jsd_GetSourceURL(JSDContext* jsdc, JSDSourceText* jsdsrc)
michael@0 255 {
michael@0 256 return jsdsrc->url;
michael@0 257 }
michael@0 258
michael@0 259 bool
michael@0 260 jsd_GetSourceText(JSDContext* jsdc, JSDSourceText* jsdsrc,
michael@0 261 const char** ppBuf, int* pLen )
michael@0 262 {
michael@0 263 *ppBuf = jsdsrc->text;
michael@0 264 *pLen = jsdsrc->textLength;
michael@0 265 return true;
michael@0 266 }
michael@0 267
michael@0 268 void
michael@0 269 jsd_ClearSourceText(JSDContext* jsdc, JSDSourceText* jsdsrc)
michael@0 270 {
michael@0 271 if( JSD_SOURCE_INITED != jsdsrc->status &&
michael@0 272 JSD_SOURCE_PARTIAL != jsdsrc->status )
michael@0 273 {
michael@0 274 _clearText(jsdc, jsdsrc);
michael@0 275 }
michael@0 276 }
michael@0 277
michael@0 278 JSDSourceStatus
michael@0 279 jsd_GetSourceStatus(JSDContext* jsdc, JSDSourceText* jsdsrc)
michael@0 280 {
michael@0 281 return jsdsrc->status;
michael@0 282 }
michael@0 283
michael@0 284 bool
michael@0 285 jsd_IsSourceDirty(JSDContext* jsdc, JSDSourceText* jsdsrc)
michael@0 286 {
michael@0 287 return jsdsrc->dirty;
michael@0 288 }
michael@0 289
michael@0 290 void
michael@0 291 jsd_SetSourceDirty(JSDContext* jsdc, JSDSourceText* jsdsrc, bool dirty)
michael@0 292 {
michael@0 293 jsdsrc->dirty = dirty;
michael@0 294 }
michael@0 295
michael@0 296 unsigned
michael@0 297 jsd_GetSourceAlterCount(JSDContext* jsdc, JSDSourceText* jsdsrc)
michael@0 298 {
michael@0 299 return jsdsrc->alterCount;
michael@0 300 }
michael@0 301
michael@0 302 unsigned
michael@0 303 jsd_IncrementSourceAlterCount(JSDContext* jsdc, JSDSourceText* jsdsrc)
michael@0 304 {
michael@0 305 return jsdsrc->alterCount = jsdc->sourceAlterCount++;
michael@0 306 }
michael@0 307
michael@0 308 /***************************************************************************/
michael@0 309
michael@0 310 #if defined(DEBUG) && 0
michael@0 311 void DEBUG_ITERATE_SOURCES( JSDContext* jsdc )
michael@0 312 {
michael@0 313 JSDSourceText* iterp = nullptr;
michael@0 314 JSDSourceText* jsdsrc = nullptr;
michael@0 315 int dummy;
michael@0 316
michael@0 317 while( nullptr != (jsdsrc = jsd_IterateSources(jsdc, &iterp)) )
michael@0 318 {
michael@0 319 const char* url;
michael@0 320 const char* text;
michael@0 321 int len;
michael@0 322 bool dirty;
michael@0 323 JSDStreamStatus status;
michael@0 324 bool gotSrc;
michael@0 325
michael@0 326 url = JSD_GetSourceURL(jsdc, jsdsrc);
michael@0 327 dirty = JSD_IsSourceDirty(jsdc, jsdsrc);
michael@0 328 status = JSD_GetSourceStatus(jsdc, jsdsrc);
michael@0 329 gotSrc = JSD_GetSourceText(jsdc, jsdsrc, &text, &len );
michael@0 330
michael@0 331 dummy = 0; /* gives us a line to set breakpoint... */
michael@0 332 }
michael@0 333 }
michael@0 334 #else
michael@0 335 #define DEBUG_ITERATE_SOURCES(x) ((void)x)
michael@0 336 #endif
michael@0 337
michael@0 338 /***************************************************************************/
michael@0 339
michael@0 340 JSDSourceText*
michael@0 341 jsd_NewSourceText(JSDContext* jsdc, const char* url)
michael@0 342 {
michael@0 343 JSDSourceText* jsdsrc;
michael@0 344 char* new_url_string;
michael@0 345
michael@0 346 JSD_LOCK_SOURCE_TEXT(jsdc);
michael@0 347
michael@0 348 new_url_string = jsd_BuildNormalizedURL(url);
michael@0 349
michael@0 350 if( ! new_url_string )
michael@0 351 return nullptr;
michael@0 352
michael@0 353 jsdsrc = jsd_FindSourceForURL(jsdc, new_url_string);
michael@0 354
michael@0 355 if( jsdsrc )
michael@0 356 {
michael@0 357 if( jsdsrc->doingEval )
michael@0 358 {
michael@0 359 free(new_url_string);
michael@0 360 JSD_UNLOCK_SOURCE_TEXT(jsdc);
michael@0 361 return nullptr;
michael@0 362 }
michael@0 363 else
michael@0 364 _moveSourceToRemovedList(jsdc, jsdsrc);
michael@0 365 }
michael@0 366
michael@0 367 jsdsrc = _addSource( jsdc, new_url_string );
michael@0 368
michael@0 369 JSD_UNLOCK_SOURCE_TEXT(jsdc);
michael@0 370
michael@0 371 return jsdsrc;
michael@0 372 }
michael@0 373
michael@0 374 JSDSourceText*
michael@0 375 jsd_AppendSourceText(JSDContext* jsdc,
michael@0 376 JSDSourceText* jsdsrc,
michael@0 377 const char* text, /* *not* zero terminated */
michael@0 378 size_t length,
michael@0 379 JSDSourceStatus status)
michael@0 380 {
michael@0 381 JSD_LOCK_SOURCE_TEXT(jsdc);
michael@0 382
michael@0 383 if( jsdsrc->doingEval )
michael@0 384 {
michael@0 385 JSD_UNLOCK_SOURCE_TEXT(jsdc);
michael@0 386 return nullptr;
michael@0 387 }
michael@0 388
michael@0 389 if( ! _isSourceInSourceList( jsdc, jsdsrc ) )
michael@0 390 {
michael@0 391 _removeSourceFromRemovedList( jsdc, jsdsrc );
michael@0 392 JSD_UNLOCK_SOURCE_TEXT(jsdc);
michael@0 393 return nullptr;
michael@0 394 }
michael@0 395
michael@0 396 if( text && length && ! _appendText( jsdc, jsdsrc, text, length ) )
michael@0 397 {
michael@0 398 jsdsrc->dirty = true;
michael@0 399 jsdsrc->alterCount = jsdc->sourceAlterCount++ ;
michael@0 400 jsdsrc->status = JSD_SOURCE_FAILED;
michael@0 401 _moveSourceToRemovedList(jsdc, jsdsrc);
michael@0 402 JSD_UNLOCK_SOURCE_TEXT(jsdc);
michael@0 403 return nullptr;
michael@0 404 }
michael@0 405
michael@0 406 jsdsrc->dirty = true;
michael@0 407 jsdsrc->alterCount = jsdc->sourceAlterCount++ ;
michael@0 408 jsdsrc->status = status;
michael@0 409 DEBUG_ITERATE_SOURCES(jsdc);
michael@0 410 JSD_UNLOCK_SOURCE_TEXT(jsdc);
michael@0 411 return jsdsrc;
michael@0 412 }
michael@0 413
michael@0 414 JSDSourceText*
michael@0 415 jsd_AppendUCSourceText(JSDContext* jsdc,
michael@0 416 JSDSourceText* jsdsrc,
michael@0 417 const jschar* text, /* *not* zero terminated */
michael@0 418 size_t length,
michael@0 419 JSDSourceStatus status)
michael@0 420 {
michael@0 421 #define UNICODE_TRUNCATE_BUF_SIZE 1024
michael@0 422 static char* buf = nullptr;
michael@0 423 int remaining = length;
michael@0 424
michael@0 425 if(!text || !length)
michael@0 426 return jsd_AppendSourceText(jsdc, jsdsrc, nullptr, 0, status);
michael@0 427
michael@0 428 JSD_LOCK_SOURCE_TEXT(jsdc);
michael@0 429 if(!buf)
michael@0 430 {
michael@0 431 buf = js_pod_malloc<char>(UNICODE_TRUNCATE_BUF_SIZE);
michael@0 432 if(!buf)
michael@0 433 {
michael@0 434 JSD_UNLOCK_SOURCE_TEXT(jsdc);
michael@0 435 return nullptr;
michael@0 436 }
michael@0 437 }
michael@0 438 while(remaining && jsdsrc) {
michael@0 439 int bytes = (remaining < UNICODE_TRUNCATE_BUF_SIZE) ? remaining : UNICODE_TRUNCATE_BUF_SIZE;
michael@0 440 int i;
michael@0 441 for(i = 0; i < bytes; i++)
michael@0 442 buf[i] = (const char) *(text++);
michael@0 443 jsdsrc = jsd_AppendSourceText(jsdc,jsdsrc,
michael@0 444 buf, bytes,
michael@0 445 JSD_SOURCE_PARTIAL);
michael@0 446 remaining -= bytes;
michael@0 447 }
michael@0 448 if(jsdsrc && status != JSD_SOURCE_PARTIAL)
michael@0 449 jsdsrc = jsd_AppendSourceText(jsdc, jsdsrc, nullptr, 0, status);
michael@0 450
michael@0 451 JSD_UNLOCK_SOURCE_TEXT(jsdc);
michael@0 452 return jsdsrc;
michael@0 453 }
michael@0 454
michael@0 455 /* convienence function for adding complete source of url in one call */
michael@0 456 bool
michael@0 457 jsd_AddFullSourceText(JSDContext* jsdc,
michael@0 458 const char* text, /* *not* zero terminated */
michael@0 459 size_t length,
michael@0 460 const char* url)
michael@0 461 {
michael@0 462 JSDSourceText* jsdsrc;
michael@0 463
michael@0 464 JSD_LOCK_SOURCE_TEXT(jsdc);
michael@0 465
michael@0 466 jsdsrc = jsd_NewSourceText(jsdc, url);
michael@0 467 if( jsdsrc )
michael@0 468 jsdsrc = jsd_AppendSourceText(jsdc, jsdsrc,
michael@0 469 text, length, JSD_SOURCE_PARTIAL );
michael@0 470 if( jsdsrc )
michael@0 471 jsdsrc = jsd_AppendSourceText(jsdc, jsdsrc,
michael@0 472 nullptr, 0, JSD_SOURCE_COMPLETED );
michael@0 473
michael@0 474 JSD_UNLOCK_SOURCE_TEXT(jsdc);
michael@0 475
michael@0 476 return jsdsrc ? true : false;
michael@0 477 }
michael@0 478
michael@0 479 /***************************************************************************/
michael@0 480
michael@0 481 void
michael@0 482 jsd_StartingEvalUsingFilename(JSDContext* jsdc, const char* url)
michael@0 483 {
michael@0 484 JSDSourceText* jsdsrc;
michael@0 485
michael@0 486 /* NOTE: We leave it locked! */
michael@0 487 JSD_LOCK_SOURCE_TEXT(jsdc);
michael@0 488
michael@0 489 jsdsrc = jsd_FindSourceForURL(jsdc, url);
michael@0 490 if(jsdsrc)
michael@0 491 {
michael@0 492 #if 0
michael@0 493 #ifndef JSD_LOWLEVEL_SOURCE
michael@0 494 MOZ_ASSERT(! jsdsrc->doingEval);
michael@0 495 #endif
michael@0 496 #endif
michael@0 497 jsdsrc->doingEval = true;
michael@0 498 }
michael@0 499 }
michael@0 500
michael@0 501 void
michael@0 502 jsd_FinishedEvalUsingFilename(JSDContext* jsdc, const char* url)
michael@0 503 {
michael@0 504 JSDSourceText* jsdsrc;
michael@0 505
michael@0 506 /* NOTE: We ASSUME it is locked! */
michael@0 507
michael@0 508 jsdsrc = jsd_FindSourceForURL(jsdc, url);
michael@0 509 if(jsdsrc)
michael@0 510 {
michael@0 511 #if 0
michael@0 512 #ifndef JSD_LOWLEVEL_SOURCE
michael@0 513 /*
michael@0 514 * when using this low level source addition, this jsdsrc might
michael@0 515 * not have existed before the eval, but does exist now (without
michael@0 516 * this flag set!)
michael@0 517 */
michael@0 518 MOZ_ASSERT(jsdsrc->doingEval);
michael@0 519 #endif
michael@0 520 #endif
michael@0 521 jsdsrc->doingEval = false;
michael@0 522 }
michael@0 523
michael@0 524 JSD_UNLOCK_SOURCE_TEXT(jsdc);
michael@0 525 }

mercurial