Add pcrs_strerror() message for PCRE_ERROR_MATCHLIMIT
[privoxy.git] / pcrs.c
1 const char pcrs_rcs[] = "$Id: pcrs.c,v 1.21 2006/07/18 14:48:47 david__schmidt Exp $";
2
3 /*********************************************************************
4  *
5  * File        :  $Source: /cvsroot/ijbswa/current/pcrs.c,v $
6  *
7  * Purpose     :  pcrs is a supplement to the pcre library by Philip Hazel
8  *                <ph10@cam.ac.uk> and adds Perl-style substitution. That
9  *                is, it mimics Perl's 's' operator. See pcrs(3) for details.
10  *
11  *
12  * Copyright   :  Written and Copyright (C) 2000, 2001 by Andreas S. Oesterhelt
13  *                <andreas@oesterhelt.org>
14  *
15  *                This program is free software; you can redistribute it 
16  *                and/or modify it under the terms of the GNU Lesser
17  *                General Public License (LGPL), version 2.1, which  should
18  *                be included in this distribution (see LICENSE.txt), with
19  *                the exception that the permission to replace that license
20  *                with the GNU General Public License (GPL) given in section
21  *                3 is restricted to version 2 of the GPL.
22  *
23  *                This program is distributed in the hope that it will
24  *                be useful, but WITHOUT ANY WARRANTY; without even the
25  *                implied warranty of MERCHANTABILITY or FITNESS FOR A
26  *                PARTICULAR PURPOSE.  See the license for more details.
27  *
28  *                The GNU Lesser General Public License should be included
29  *                with this file.  If not, you can view it at
30  *                http://www.gnu.org/licenses/lgpl.html
31  *                or write to the Free Software Foundation, Inc., 59
32  *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
33  *
34  * Revisions   :
35  *    $Log: pcrs.c,v $
36  *    Revision 1.21  2006/07/18 14:48:47  david__schmidt
37  *    Reorganizing the repository: swapping out what was HEAD (the old 3.1 branch)
38  *    with what was really the latest development (the v_3_0_branch branch)
39  *
40  *    Revision 1.19.2.4  2005/05/07 21:50:55  david__schmidt
41  *    A few memory leaks plugged (mostly on error paths)
42  *
43  *    Revision 1.19.2.3  2003/12/04 12:32:45  oes
44  *    Append a trailing nullbyte to result to facilitate string processing
45  *
46  *    Revision 1.19.2.2  2002/10/08 16:22:28  oes
47  *    Bugfix: Need to check validity of backreferences explicitly,
48  *    because when max_matches are reached and matches is expanded,
49  *    realloc() does not zero the memory. Fixes Bug # 606227
50  *
51  *    Revision 1.19.2.1  2002/08/10 11:23:40  oes
52  *    Include prce.h via project.h, where the appropriate
53  *    source will have been selected
54  *
55  *    Revision 1.19  2002/03/08 14:47:48  oes
56  *    Cosmetics
57  *
58  *    Revision 1.18  2002/03/08 14:17:14  oes
59  *    Fixing -Wconversion warnings
60  *
61  *    Revision 1.17  2002/03/08 13:45:48  oes
62  *    Hiding internal functions
63  *
64  *    Revision 1.16  2001/11/30 21:32:14  jongfoster
65  *    Fixing signed/unsigned comparison (Andreas please check this!)
66  *    One tab->space
67  *
68  *    Revision 1.15  2001/09/20 16:11:06  steudten
69  *
70  *    Add casting for some string functions.
71  *
72  *    Revision 1.14  2001/09/09 21:41:57  oes
73  *    Fixing yet another silly bug
74  *
75  *    Revision 1.13  2001/09/06 14:05:59  oes
76  *    Fixed silly bug
77  *
78  *    Revision 1.12  2001/08/18 11:35:00  oes
79  *    - Introduced pcrs_strerror()
80  *    - made some NULL arguments non-fatal
81  *    - added support for \n \r \e \b \t \f \a \0 in substitute
82  *    - made quoting adhere to standard rules
83  *    - added warning for bad backrefs
84  *    - added pcrs_execute_list()
85  *    - fixed comments
86  *    - bugfix & cosmetics
87  *
88  *    Revision 1.11  2001/08/15 15:32:03  oes
89  *     - Added support for Perl's special variables $+, $' and $`
90  *     - Improved the substitute parser
91  *     - Replaced the hard limit for the maximum number of matches
92  *       by dynamic reallocation
93  *
94  *    Revision 1.10  2001/08/05 13:13:11  jongfoster
95  *    Making parameters "const" where possible.
96  *
97  *    Revision 1.9  2001/07/18 17:27:00  oes
98  *    Changed interface; Cosmetics
99  *
100  *    Revision 1.8  2001/06/29 21:45:41  oes
101  *    Indentation, CRLF->LF, Tab-> Space
102  *
103  *    Revision 1.7  2001/06/29 13:33:04  oes
104  *    - Cleaned up, renamed and reordered functions,
105  *      improved comments
106  *    - Removed my_strsep
107  *    - Replaced globalflag with a general flags int
108  *      that holds PCRS_GLOBAL, PCRS_SUCCESS, and PCRS_TRIVIAL
109  *    - Introduced trivial option that will prevent pcrs
110  *      from honouring backreferences in the substitute,
111  *      which is useful for large substitutes that are
112  *      red in from somewhere and saves the pain of escaping
113  *      the backrefs
114  *    - Introduced convenience function pcrs_free_joblist()
115  *    - Split pcrs_make_job() into pcrs_compile(), which still
116  *      takes a complete s/// comand as argument and parses it,
117  *      and a new function pcrs_make_job, which takes the
118  *      three separate components. This should make for a
119  *      much friendlier frontend.
120  *    - Removed create_pcrs_job() which was useless
121  *    - Fixed a bug in pcrs_execute
122  *    - Success flag is now handled by pcrs instead of user
123  *
124  *    Revision 1.6  2001/06/03 19:12:45  oes
125  *    added FIXME
126  *
127  *    Revision 1.5  2001/05/29 09:50:24  jongfoster
128  *    (Fixed one int -> size_t)
129  *
130  *    Revision 1.4  2001/05/25 14:12:40  oes
131  *    Fixed bug: Empty substitutes now detected
132  *
133  *    Revision 1.3  2001/05/25 11:03:55  oes
134  *    Added sanity check for NULL jobs to pcrs_exec_substitution
135  *
136  *    Revision 1.2  2001/05/22 18:46:04  oes
137  *
138  *      Added support for PCRE_UNGREEDY behaviour to pcrs,
139  *      which is selected by the (nonstandard and therefore
140  *      capital) letter 'U' in the option string.
141  *      It causes the quantifiers to be ungreedy by default.
142  *      Appending a ? turns back to greedy (!).
143  *
144  *    Revision 1.1.1.1  2001/05/15 13:59:02  oes
145  *    Initial import of version 2.9.3 source tree
146  *
147  *
148  *********************************************************************/
149 \f
150
151 /*
152  * Include project.h just so that the right pcre.h gets
153  * included from there
154  */
155 #include "project.h"
156
157 #include <string.h>
158 #include <ctype.h>
159
160 #include "pcrs.h"
161
162 const char pcrs_h_rcs[] = PCRS_H_VERSION;
163
164 /*
165  * Internal prototypes
166  */
167
168 static int              pcrs_parse_perl_options(const char *optstring, int *flags);
169 static pcrs_substitute *pcrs_compile_replacement(const char *replacement, int trivialflag,
170                         int capturecount, int *errptr);
171
172 /*********************************************************************
173  *
174  * Function    :  pcrs_strerror
175  *
176  * Description :  Return a string describing a given error code.
177  *             
178  * Parameters  :
179  *          1  :  error = the error code
180  *
181  * Returns     :  char * to the descriptive string
182  *
183  *********************************************************************/
184 const char *pcrs_strerror(const int error)
185 {
186    if (error < 0)
187    {
188       switch (error)
189       {
190          /* Passed-through PCRE error: */
191          case PCRE_ERROR_NOMEMORY:     return "(pcre:) No memory";
192
193          /* Shouldn't happen unless PCRE or PCRS bug, or user messed with compiled job: */
194          case PCRE_ERROR_NULL:         return "(pcre:) NULL code or subject or ovector";
195          case PCRE_ERROR_BADOPTION:    return "(pcre:) Unrecognized option bit";
196          case PCRE_ERROR_BADMAGIC:     return "(pcre:) Bad magic number in code";
197          case PCRE_ERROR_UNKNOWN_NODE: return "(pcre:) Bad node in pattern";
198
199          /* Can't happen / not passed: */
200          case PCRE_ERROR_NOSUBSTRING:  return "(pcre:) Fire in power supply"; 
201          case PCRE_ERROR_NOMATCH:      return "(pcre:) Water in power supply";
202
203 #ifdef PCRE_ERROR_MATCHLIMIT
204          /*
205           * Only reported by PCRE versions newer than our own.
206           */
207          case PCRE_ERROR_MATCHLIMIT:   return "(pcre:) Match limit reached";
208 #endif /* def PCRE_ERROR_MATCHLIMIT */
209
210          /* PCRS errors: */
211          case PCRS_ERR_NOMEM:          return "(pcrs:) No memory";
212          case PCRS_ERR_CMDSYNTAX:      return "(pcrs:) Syntax error while parsing command";
213          case PCRS_ERR_STUDY:          return "(pcrs:) PCRE error while studying the pattern";
214          case PCRS_ERR_BADJOB:         return "(pcrs:) Bad job - NULL job, pattern or substitute";
215          case PCRS_WARN_BADREF:        return "(pcrs:) Backreference out of range";
216
217          /* 
218           * XXX: With the exception of PCRE_ERROR_MATCHLIMIT we
219           * only catch PCRE errors that can happen with our internal
220           * version. If Privoxy is linked against a newer
221           * PCRE version all bets are off ...
222           */
223          default:  return "Unknown error. Privoxy out of sync with PCRE?";
224       }
225    }
226    /* error >= 0: No error */
227    return "(pcrs:) Everything's just fine. Thanks for asking.";
228
229 }
230
231
232 /*********************************************************************
233  *
234  * Function    :  pcrs_parse_perl_options
235  *
236  * Description :  This function parses a string containing the options to
237  *                Perl's s/// operator. It returns an integer that is the
238  *                pcre equivalent of the symbolic optstring.
239  *                Since pcre doesn't know about Perl's 'g' (global) or pcrs',
240  *                'T' (trivial) options but pcrs needs them, the corresponding
241  *                flags are set if 'g'or 'T' is encountered.
242  *                Note: The 'T' and 'U' options do not conform to Perl.
243  *             
244  * Parameters  :
245  *          1  :  optstring = string with options in perl syntax
246  *          2  :  flags = see description
247  *
248  * Returns     :  option integer suitable for pcre 
249  *
250  *********************************************************************/
251 static int pcrs_parse_perl_options(const char *optstring, int *flags)
252 {
253    size_t i;
254    int rc = 0;
255    *flags = 0;
256
257    if (NULL == optstring) return 0;
258
259    for (i = 0; i < strlen(optstring); i++)
260    {
261       switch(optstring[i])
262       {
263          case 'e': break; /* ToDo ;-) */
264          case 'g': *flags |= PCRS_GLOBAL; break;
265          case 'i': rc |= PCRE_CASELESS; break;
266          case 'm': rc |= PCRE_MULTILINE; break;
267          case 'o': break;
268          case 's': rc |= PCRE_DOTALL; break;
269          case 'x': rc |= PCRE_EXTENDED; break;
270          case 'U': rc |= PCRE_UNGREEDY; break;
271          case 'T': *flags |= PCRS_TRIVIAL; break;
272          default: break;
273       }
274    }
275    return rc;
276
277 }
278
279
280 /*********************************************************************
281  *
282  * Function    :  pcrs_compile_replacement
283  *
284  * Description :  This function takes a Perl-style replacement (2nd argument
285  *                to the s/// operator and returns a compiled pcrs_substitute,
286  *                or NULL if memory allocation for the substitute structure
287  *                fails.
288  *
289  * Parameters  :
290  *          1  :  replacement = replacement part of s/// operator
291  *                              in perl syntax
292  *          2  :  trivialflag = Flag that causes backreferences to be
293  *                              ignored.
294  *          3  :  capturecount = Number of capturing subpatterns in
295  *                               the pattern. Needed for $+ handling.
296  *          4  :  errptr = pointer to an integer in which error
297  *                         conditions can be returned.
298  *
299  * Returns     :  pcrs_substitute data structure, or NULL if an
300  *                error is encountered. In that case, *errptr has
301  *                the reason.
302  *
303  *********************************************************************/
304 static pcrs_substitute *pcrs_compile_replacement(const char *replacement, int trivialflag, int capturecount, int *errptr)
305 {
306    int i, k, l, quoted;
307    size_t length;
308    char *text;
309    pcrs_substitute *r;
310
311    i = k = l = quoted = 0;
312
313    /*
314     * Sanity check
315     */
316    if (NULL == replacement)
317    {
318       replacement = "";
319    }
320
321    /*
322     * Get memory or fail
323     */
324    if (NULL == (r = (pcrs_substitute *)malloc(sizeof(pcrs_substitute))))
325    {
326       *errptr = PCRS_ERR_NOMEM;
327       return NULL;
328    }
329    memset(r, '\0', sizeof(pcrs_substitute));
330
331    length = strlen(replacement);
332
333    if (NULL == (text = (char *)malloc(length + 1)))
334    {
335       free(r);
336       *errptr = PCRS_ERR_NOMEM;
337       return NULL;
338    }
339    memset(text, '\0', length + 1);
340    
341
342    /*
343     * In trivial mode, just copy the substitute text
344     */
345    if (trivialflag)
346    {
347       text = strncpy(text, replacement, length + 1);
348       k = length;
349    }
350
351    /*
352     * Else, parse, cut out and record all backreferences
353     */
354    else
355    {
356       while (i < (int)length)
357       {
358          /* Quoting */
359          if (replacement[i] == '\\')
360          {
361             if (quoted)
362             {
363                text[k++] = replacement[i++];
364                quoted = 0;
365             }
366             else
367             {
368                if (replacement[i+1] && strchr("tnrfae0", replacement[i+1]))
369                {
370                   switch (replacement[++i])
371                   {
372                   case 't':
373                      text[k++] = '\t';
374                      break;
375                   case 'n':
376                      text[k++] = '\n';
377                      break;
378                   case 'r':
379                      text[k++] = '\r';
380                      break;
381                   case 'f':
382                      text[k++] = '\f';
383                      break;
384                   case 'a':
385                      text[k++] = 7;
386                      break;
387                   case 'e':
388                      text[k++] = 27;
389                      break;
390                   case '0':
391                      text[k++] = '\0';
392                      break;
393                   }
394                   i++;
395                }
396                else
397                {
398                   quoted = 1;
399                   i++;
400                }
401             }
402             continue;
403          }
404
405          /* Backreferences */
406          if (replacement[i] == '$' && !quoted && i < (int)(length - 1))
407          {
408             char *symbol, symbols[] = "'`+&";
409             r->block_length[l] = k - r->block_offset[l];
410
411             /* Numerical backreferences */
412             if (isdigit((int)replacement[i + 1]))
413             {
414                while (i < (int)length && isdigit((int)replacement[++i]))
415                {
416                   r->backref[l] = r->backref[l] * 10 + replacement[i] - 48;
417                }
418                if (r->backref[l] > capturecount)
419                {
420                   *errptr = PCRS_WARN_BADREF;
421                }
422             }
423
424             /* Symbolic backreferences: */
425             else if (NULL != (symbol = strchr(symbols, replacement[i + 1])))
426             {
427                
428                if (symbol - symbols == 2) /* $+ */
429                {
430                   r->backref[l] = capturecount;
431                }
432                else if (symbol - symbols == 3) /* $& */
433                {
434                   r->backref[l] = 0;
435                }
436                else /* $' or $` */
437                {
438                   r->backref[l] = PCRS_MAX_SUBMATCHES + 1 - (symbol - symbols);
439                }
440                i += 2;
441             }
442
443             /* Invalid backref -> plain '$' */
444             else
445             {
446                goto plainchar;
447             }
448
449             /* Valid and in range? -> record */
450             if (r->backref[l] < PCRS_MAX_SUBMATCHES + 2)
451             {
452                r->backref_count[r->backref[l]] += 1;
453                r->block_offset[++l] = k;
454             }
455             else
456             {
457                *errptr = PCRS_WARN_BADREF;
458             }   
459             continue;
460          }
461          
462 plainchar:
463          /* Plain chars are copied */
464          text[k++] = replacement[i++];
465          quoted = 0;
466       }
467    } /* -END- if (!trivialflag) */
468
469    /*
470     * Finish & return
471     */
472    r->text = text;
473    r->backrefs = l;
474    r->block_length[l] = k - r->block_offset[l];
475
476    return r;
477
478 }
479
480
481 /*********************************************************************
482  *
483  * Function    :  pcrs_free_job
484  *
485  * Description :  Frees the memory used by a pcrs_job struct and its
486  *                dependant structures.
487  *
488  * Parameters  :
489  *          1  :  job = pointer to the pcrs_job structure to be freed
490  *
491  * Returns     :  a pointer to the next job, if there was any, or
492  *                NULL otherwise. 
493  *
494  *********************************************************************/
495 pcrs_job *pcrs_free_job(pcrs_job *job)
496 {
497    pcrs_job *next;
498
499    if (job == NULL)
500    {
501       return NULL;
502    }
503    else
504    {
505       next = job->next;
506       if (job->pattern != NULL) free(job->pattern);
507       if (job->hints != NULL) free(job->hints);
508       if (job->substitute != NULL)
509       {
510          if (job->substitute->text != NULL) free(job->substitute->text);
511          free(job->substitute);
512       }
513       free(job);
514    }
515    return next;
516
517 }
518
519
520 /*********************************************************************
521  *
522  * Function    :  pcrs_free_joblist
523  *
524  * Description :  Iterates through a chained list of pcrs_job's and
525  *                frees them using pcrs_free_job.
526  *
527  * Parameters  :
528  *          1  :  joblist = pointer to the first pcrs_job structure to
529  *                be freed
530  *
531  * Returns     :  N/A
532  *
533  *********************************************************************/
534 void pcrs_free_joblist(pcrs_job *joblist)
535 {
536    while ( NULL != (joblist = pcrs_free_job(joblist)) ) {};
537
538    return;
539
540 }
541
542
543 /*********************************************************************
544  *
545  * Function    :  pcrs_compile_command
546  *
547  * Description :  Parses a string with a Perl-style s/// command, 
548  *                calls pcrs_compile, and returns a corresponding
549  *                pcrs_job, or NULL if parsing or compiling the job
550  *                fails.
551  *
552  * Parameters  :
553  *          1  :  command = string with perl-style s/// command
554  *          2  :  errptr = pointer to an integer in which error
555  *                         conditions can be returned.
556  *
557  * Returns     :  a corresponding pcrs_job data structure, or NULL
558  *                if an error was encountered. In that case, *errptr
559  *                has the reason.
560  *
561  *********************************************************************/
562 pcrs_job *pcrs_compile_command(const char *command, int *errptr)
563 {
564    int i, k, l, quoted = FALSE;
565    size_t limit;
566    char delimiter;
567    char *tokens[4];   
568    pcrs_job *newjob;
569    
570    i = k = l = 0;
571    
572    /*
573     * Tokenize the perl command
574     */
575    limit = strlen(command);
576    if (limit < 4)
577    {
578       *errptr = PCRS_ERR_CMDSYNTAX;
579       return NULL;
580    }
581    else
582    {
583       delimiter = command[1];
584    }
585
586    tokens[l] = (char *) malloc(limit + 1);
587
588    for (i = 0; i <= (int)limit; i++)
589    {
590       
591       if (command[i] == delimiter && !quoted)
592       {
593          if (l == 3)
594          {
595             l = -1;
596             break;
597          }
598          tokens[0][k++] = '\0';
599          tokens[++l] = tokens[0] + k;
600          continue;
601       }
602       
603       else if (command[i] == '\\' && !quoted)
604       {
605          quoted = TRUE;
606          if (command[i+1] == delimiter) continue;
607       }
608       else
609       {
610          quoted = FALSE;
611       }
612       tokens[0][k++] = command[i];
613    }
614
615    /*
616     * Syntax error ?
617     */
618    if (l != 3)
619    {
620       *errptr = PCRS_ERR_CMDSYNTAX;
621       free(tokens[0]);
622       return NULL;
623    }
624    
625    newjob = pcrs_compile(tokens[1], tokens[2], tokens[3], errptr);
626    free(tokens[0]);
627    return newjob;
628    
629 }
630
631
632 /*********************************************************************
633  *
634  * Function    :  pcrs_compile
635  *
636  * Description :  Takes the three arguments to a perl s/// command
637  *                and compiles a pcrs_job structure from them.
638  *
639  * Parameters  :
640  *          1  :  pattern = string with perl-style pattern
641  *          2  :  substitute = string with perl-style substitute
642  *          3  :  options = string with perl-style options
643  *          4  :  errptr = pointer to an integer in which error
644  *                         conditions can be returned.
645  *
646  * Returns     :  a corresponding pcrs_job data structure, or NULL
647  *                if an error was encountered. In that case, *errptr
648  *                has the reason.
649  *
650  *********************************************************************/
651 pcrs_job *pcrs_compile(const char *pattern, const char *substitute, const char *options, int *errptr)
652 {
653    pcrs_job *newjob;
654    int flags;
655    int capturecount;
656    const char *error;
657
658    *errptr = 0;
659
660    /* 
661     * Handle NULL arguments
662     */
663    if (pattern == NULL) pattern = "";
664    if (substitute == NULL) substitute = "";
665
666
667    /* 
668     * Get and init memory
669     */
670    if (NULL == (newjob = (pcrs_job *)malloc(sizeof(pcrs_job))))
671    {
672       *errptr = PCRS_ERR_NOMEM;
673       return NULL;
674    }
675    memset(newjob, '\0', sizeof(pcrs_job));
676
677
678    /*
679     * Evaluate the options
680     */
681    newjob->options = pcrs_parse_perl_options(options, &flags);
682    newjob->flags = flags;
683
684
685    /*
686     * Compile the pattern
687     */
688    newjob->pattern = pcre_compile(pattern, newjob->options, &error, errptr, NULL);
689    if (newjob->pattern == NULL)
690    {
691       pcrs_free_job(newjob);
692       return NULL;
693    }
694
695
696    /*
697     * Generate hints. This has little overhead, since the
698     * hints will be NULL for a boring pattern anyway.
699     */
700    newjob->hints = pcre_study(newjob->pattern, 0, &error);
701    if (error != NULL)
702    {
703       *errptr = PCRS_ERR_STUDY;
704       pcrs_free_job(newjob);
705       return NULL;
706    }
707  
708
709    /* 
710     * Determine the number of capturing subpatterns. 
711     * This is needed for handling $+ in the substitute.
712     */
713    if (0 > (*errptr = pcre_fullinfo(newjob->pattern, newjob->hints, PCRE_INFO_CAPTURECOUNT, &capturecount)))
714    {
715       pcrs_free_job(newjob);
716       return NULL;
717    }
718  
719
720    /*
721     * Compile the substitute
722     */
723    if (NULL == (newjob->substitute = pcrs_compile_replacement(substitute, newjob->flags & PCRS_TRIVIAL, capturecount, errptr)))
724    {
725       pcrs_free_job(newjob);
726       return NULL;
727    }
728  
729    return newjob;
730
731 }
732
733
734 /*********************************************************************
735  *
736  * Function    :  pcrs_execute_list
737  *
738  * Description :  This is a multiple job wrapper for pcrs_execute().
739  *                Apply the regular substitutions defined by the jobs in
740  *                the joblist to the subject.
741  *                The subject itself is left untouched, memory for the result
742  *                is malloc()ed and it is the caller's responsibility to free
743  *                the result when it's no longer needed. 
744  *
745  *                Note: For convenient string handling, a null byte is
746  *                      appended to the result. It does not count towards the
747  *                      result_length, though.
748  *
749  *
750  * Parameters  :
751  *          1  :  joblist = the chained list of pcrs_jobs to be executed
752  *          2  :  subject = the subject string
753  *          3  :  subject_length = the subject's length 
754  *          4  :  result = char** for returning  the result 
755  *          5  :  result_length = size_t* for returning the result's length
756  *
757  * Returns     :  On success, the number of substitutions that were made.
758  *                 May be > 1 if job->flags contained PCRS_GLOBAL
759  *                On failure, the (negative) pcre error code describing the
760  *                 failure, which may be translated to text using pcrs_strerror().
761  *
762  *********************************************************************/
763 int pcrs_execute_list(pcrs_job *joblist, char *subject, size_t subject_length, char **result, size_t *result_length)
764 {
765    pcrs_job *job;
766    char *old, *new = NULL;
767    int hits, total_hits;
768  
769    old = subject;
770    *result_length = subject_length;
771    hits = total_hits = 0;
772
773    for (job = joblist; job != NULL; job = job->next)
774    {
775       hits = pcrs_execute(job, old, *result_length, &new, result_length);
776
777       if (old != subject) free(old);
778
779       if (hits < 0)
780       {
781          return(hits);
782       }
783       else
784       {
785          total_hits += hits;
786          old = new;
787       }
788    }
789
790    *result = new;
791    return(total_hits);
792
793 }
794
795
796 /*********************************************************************
797  *
798  * Function    :  pcrs_execute
799  *
800  * Description :  Apply the regular substitution defined by the job to the
801  *                subject.
802  *                The subject itself is left untouched, memory for the result
803  *                is malloc()ed and it is the caller's responsibility to free
804  *                the result when it's no longer needed.
805  *
806  *                Note: For convenient string handling, a null byte is
807  *                      appended to the result. It does not count towards the
808  *                      result_length, though.
809  *
810  * Parameters  :
811  *          1  :  job = the pcrs_job to be executed
812  *          2  :  subject = the subject (== original) string
813  *          3  :  subject_length = the subject's length 
814  *          4  :  result = char** for returning  the result 
815  *          5  :  result_length = size_t* for returning the result's length
816  *
817  * Returns     :  On success, the number of substitutions that were made.
818  *                 May be > 1 if job->flags contained PCRS_GLOBAL
819  *                On failure, the (negative) pcre error code describing the
820  *                 failure, which may be translated to text using pcrs_strerror().
821  *
822  *********************************************************************/
823 int pcrs_execute(pcrs_job *job, char *subject, size_t subject_length, char **result, size_t *result_length)
824 {
825    int offsets[3 * PCRS_MAX_SUBMATCHES],
826        offset,
827        i, k,
828        matches_found,
829        submatches,
830        max_matches = PCRS_MAX_MATCH_INIT;
831    size_t newsize;
832    pcrs_match *matches, *dummy;
833    char *result_offset;
834
835    offset = i = k = 0;
836
837    /* 
838     * Sanity check & memory allocation
839     */
840    if (job == NULL || job->pattern == NULL || job->substitute == NULL || NULL == subject)
841    {
842       *result = NULL;
843       return(PCRS_ERR_BADJOB);
844    }
845
846    if (NULL == (matches = (pcrs_match *)malloc(max_matches * sizeof(pcrs_match))))
847    {
848       *result = NULL;
849       return(PCRS_ERR_NOMEM);
850    }
851    memset(matches, '\0', max_matches * sizeof(pcrs_match));
852
853
854    /*
855     * Find the pattern and calculate the space
856     * requirements for the result
857     */
858    newsize = subject_length;
859
860    while ((submatches = pcre_exec(job->pattern, job->hints, subject, (int)subject_length, offset, 0, offsets, 3 * PCRS_MAX_SUBMATCHES)) > 0)
861    {
862       job->flags |= PCRS_SUCCESS;
863       matches[i].submatches = submatches;
864
865       for (k = 0; k < submatches; k++)
866       {
867          matches[i].submatch_offset[k] = offsets[2 * k];
868
869          /* Note: Non-found optional submatches have length -1-(-1)==0 */
870          matches[i].submatch_length[k] = offsets[2 * k + 1] - offsets[2 * k]; 
871
872          /* reserve mem for each submatch as often as it is ref'd */
873          newsize += matches[i].submatch_length[k] * job->substitute->backref_count[k];
874       }
875       /* plus replacement text size minus match text size */
876       newsize += strlen(job->substitute->text) - matches[i].submatch_length[0]; 
877
878       /* chunk before match */
879       matches[i].submatch_offset[PCRS_MAX_SUBMATCHES] = 0;
880       matches[i].submatch_length[PCRS_MAX_SUBMATCHES] = offsets[0];
881       newsize += offsets[0] * job->substitute->backref_count[PCRS_MAX_SUBMATCHES];
882
883       /* chunk after match */
884       matches[i].submatch_offset[PCRS_MAX_SUBMATCHES + 1] = offsets[1];
885       matches[i].submatch_length[PCRS_MAX_SUBMATCHES + 1] = subject_length - offsets[1] - 1;
886       newsize += (subject_length - offsets[1]) * job->substitute->backref_count[PCRS_MAX_SUBMATCHES + 1];
887
888       /* Storage for matches exhausted? -> Extend! */
889       if (++i >= max_matches)
890       {
891          max_matches = (int)(max_matches * PCRS_MAX_MATCH_GROW);
892          if (NULL == (dummy = (pcrs_match *)realloc(matches, max_matches * sizeof(pcrs_match))))
893          {
894             free(matches);
895             *result = NULL;
896             return(PCRS_ERR_NOMEM);
897          }
898          matches = dummy;
899       }
900
901       /* Non-global search or limit reached? */
902       if (!(job->flags & PCRS_GLOBAL)) break;
903
904       /* Don't loop on empty matches */
905       if (offsets[1] == offset)
906          if ((size_t)offset < subject_length)
907             offset++;
908          else
909             break;
910       /* Go find the next one */
911       else
912          offset = offsets[1];
913    }
914    /* Pass pcre error through if (bad) failiure */
915    if (submatches < PCRE_ERROR_NOMATCH)
916    {
917       free(matches);
918       return submatches;   
919    }
920    matches_found = i;
921
922
923    /* 
924     * Get memory for the result (must be freed by caller!)
925     * and append terminating null byte.
926     */
927    if ((*result = (char *)malloc(newsize + 1)) == NULL)
928    {
929       free(matches);
930       return PCRS_ERR_NOMEM;
931    }
932    else
933    {
934       (*result)[newsize] = '\0';
935    }
936
937
938    /* 
939     * Replace
940     */
941    offset = 0;
942    result_offset = *result;
943
944    for (i = 0; i < matches_found; i++)
945    {
946       /* copy the chunk preceding the match */
947       memcpy(result_offset, subject + offset, (size_t)matches[i].submatch_offset[0] - offset); 
948       result_offset += matches[i].submatch_offset[0] - offset;
949
950       /* For every segment of the substitute.. */
951       for (k = 0; k <= job->substitute->backrefs; k++)
952       {
953          /* ...copy its text.. */
954          memcpy(result_offset, job->substitute->text + job->substitute->block_offset[k], job->substitute->block_length[k]);
955          result_offset += job->substitute->block_length[k];
956
957          /* ..plus, if it's not the last chunk, i.e.: There *is* a backref.. */
958          if (k != job->substitute->backrefs
959              /* ..in legal range.. */
960              && job->substitute->backref[k] < PCRS_MAX_SUBMATCHES + 2
961              /* ..and referencing a real submatch.. */
962              && job->substitute->backref[k] < matches[i].submatches
963              /* ..that is nonempty.. */
964              && matches[i].submatch_length[job->substitute->backref[k]] > 0)
965          {
966             /* ..copy the submatch that is ref'd. */
967             memcpy(
968                result_offset,
969                subject + matches[i].submatch_offset[job->substitute->backref[k]],
970                matches[i].submatch_length[job->substitute->backref[k]]
971             );
972             result_offset += matches[i].submatch_length[job->substitute->backref[k]];
973          }
974       }
975       offset =  matches[i].submatch_offset[0] + matches[i].submatch_length[0];
976    }
977
978    /* Copy the rest. */
979    memcpy(result_offset, subject + offset, subject_length - offset);
980
981    *result_length = newsize;
982    free(matches);
983    return matches_found;
984
985 }
986
987
988 /*
989   Local Variables:
990   tab-width: 3
991   end:
992 */