886cc07c8e155c56fd623e89b8044389d562fe4f
[privoxy.git] / pcrs.c
1 const char pcrs_rcs[] = "$Id: pcrs.c,v 1.11 2001/08/15 15:26:24 oes Exp $";
2
3 /*********************************************************************
4  *
5  * File        :  $Source: /cvsroot/ijbswa/current/pcrs.c,v $
6  *
7  * Purpose     :  pcrs is a supplement to the brilliant pcre library by Philip
8  *                Hazel <ph10@cam.ac.uk> and adds Perl-style substitution. That
9  *                is, it mimics Perl's 's' operator.
10  *
11  *                Currently, there's no documentation besides comments and the
12  *                source itself :-(
13  *
14  *                Note: In addition to perl's options, 'U' for ungreedy and 'T'
15  *                for trivial (i.e.: ignore backrefs in the substitute) are
16  *                supported.
17  *
18  * Copyright   :  Written and Copyright (C) 2000, 2001 by Andreas S. Oesterhelt
19  *                <andreas@oesterhelt.org>
20  *
21  *                This program is free software; you can redistribute it 
22  *                and/or modify it under the terms of the GNU General
23  *                Public License as published by the Free Software
24  *                Foundation; either version 2 of the License, or (at
25  *                your option) any later version.
26  *
27  *                This program is distributed in the hope that it will
28  *                be useful, but WITHOUT ANY WARRANTY; without even the
29  *                implied warranty of MERCHANTABILITY or FITNESS FOR A
30  *                PARTICULAR PURPOSE.  See the GNU General Public
31  *                License for more details.
32  *
33  *                The GNU General Public License should be included with
34  *                this file.  If not, you can view it at
35  *                http://www.gnu.org/copyleft/gpl.html
36  *                or write to the Free Software Foundation, Inc., 59
37  *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
38  *
39  * Revisions   :
40  *    $Log: pcrs.c,v $
41  *    Revision 1.10  2001/08/05 13:13:11  jongfoster
42  *    Making parameters "const" where possible.
43  *
44  *    Revision 1.9  2001/07/18 17:27:00  oes
45  *    Changed interface; Cosmetics
46  *
47  *    Revision 1.8  2001/06/29 21:45:41  oes
48  *    Indentation, CRLF->LF, Tab-> Space
49  *
50  *    Revision 1.7  2001/06/29 13:33:04  oes
51  *    - Cleaned up, renamed and reordered functions,
52  *      improved comments
53  *    - Removed my_strsep
54  *    - Replaced globalflag with a general flags int
55  *      that holds PCRS_GLOBAL, PCRS_SUCCESS, and PCRS_TRIVIAL
56  *    - Introduced trivial option that will prevent pcrs
57  *      from honouring backreferences in the substitute,
58  *      which is useful for large substitutes that are
59  *      red in from somewhere and saves the pain of escaping
60  *      the backrefs
61  *    - Introduced convenience function pcrs_free_joblist()
62  *    - Split pcrs_make_job() into pcrs_compile(), which still
63  *      takes a complete s/// comand as argument and parses it,
64  *      and a new function pcrs_make_job, which takes the
65  *      three separate components. This should make for a
66  *      much friendlier frontend.
67  *    - Removed create_pcrs_job() which was useless
68  *    - Fixed a bug in pcrs_execute
69  *    - Success flag is now handled by pcrs instead of user
70  *
71  *    Revision 1.6  2001/06/03 19:12:45  oes
72  *    added FIXME
73  *
74  *    Revision 1.5  2001/05/29 09:50:24  jongfoster
75  *    (Fixed one int -> size_t)
76  *
77  *    Revision 1.4  2001/05/25 14:12:40  oes
78  *    Fixed bug: Empty substitutes now detected
79  *
80  *    Revision 1.3  2001/05/25 11:03:55  oes
81  *    Added sanity check for NULL jobs to pcrs_exec_substitution
82  *
83  *    Revision 1.2  2001/05/22 18:46:04  oes
84  *
85  *      Added support for PCRE_UNGREEDY behaviour to pcrs,
86  *      which is selected by the (nonstandard and therefore
87  *      capital) letter 'U' in the option string.
88  *      It causes the quantifiers to be ungreedy by default.
89  *      Appending a ? turns back to greedy (!).
90  *
91  *    Revision 1.1.1.1  2001/05/15 13:59:02  oes
92  *    Initial import of version 2.9.3 source tree
93  *
94  *
95  *********************************************************************/
96 \f
97
98 #include <pcre.h>
99 #include <string.h>
100 #include "pcrs.h"
101 #include <stdio.h>
102 const char pcrs_h_rcs[] = PCRS_H_VERSION;
103
104
105 /*********************************************************************
106  *
107  * Function    :  pcrs_parse_perl_options
108  *
109  * Description :  This function parses a string containing the options to
110  *                Perl's s/// operator. It returns an integer that is the
111  *                pcre equivalent of the symbolic optstring.
112  *                Since pcre doesn't know about Perl's 'g' (global) or pcrs',
113  *                'T' (trivial) options but pcrs needs them, the corresponding
114  *                flags are set if 'g'or 'T' is encountered.
115  *                Note: The 'T' and 'U' options do not conform to Perl.
116  *             
117  * Parameters  :
118  *          1  :  optstring = string with options in perl syntax
119  *          2  :  flags = see description
120  *
121  * Returns     :  option integer suitable for pcre 
122  *
123  *********************************************************************/
124 int pcrs_parse_perl_options(const char *optstring, int *flags)
125 {
126    size_t i;
127    int rc = 0;
128    *flags = 0;
129    for (i=0; i < strlen(optstring); i++)
130    {
131       switch(optstring[i])
132       {
133          case 'e': break; /* ToDo ;-) */
134          case 'g': *flags |= PCRS_GLOBAL; break;
135          case 'i': rc |= PCRE_CASELESS; break;
136          case 'm': rc |= PCRE_MULTILINE; break;
137          case 'o': break;
138          case 's': rc |= PCRE_DOTALL; break;
139          case 'x': rc |= PCRE_EXTENDED; break;
140          case 'U': rc |= PCRE_UNGREEDY; break;
141            case 'T': *flags |= PCRS_TRIVIAL; break;
142          default:  break;
143       }
144    }
145    return rc;
146
147 }
148
149
150 /*********************************************************************
151  *
152  * Function    :  pcrs_compile_replacement
153  *
154  * Description :  This function takes a Perl-style replacement (2nd argument
155  *                to the s/// operator and returns a compiled pcrs_substitute,
156  *                or NULL if memory allocation for the substitute structure
157  *                fails.
158  *
159  * Parameters  :
160  *          1  :  replacement = replacement part of s/// operator
161  *                              in perl syntax
162  *          2  :  trivialflag = Flag that causes backreferences to be
163  *                              ignored.
164  *          3  :  capturecount = Number of capturing subpatterns in
165  *                               the pattern. Needed for $+ handling.
166  *          4  :  errptr = pointer to an integer in which error
167  *                         conditions can be returned.
168  *
169  * Returns     :  pcrs_substitute data structure, or NULL if an
170  *                error is encountered. In that case, *errptr has
171  *                the reason.
172  *
173  *********************************************************************/
174 pcrs_substitute *pcrs_compile_replacement(const char *replacement, int trivialflag, int capturecount, int *errptr)
175 {
176    int length, i, k, l, quoted;
177    char *text;
178    pcrs_substitute *r;
179
180    i = k = l = quoted = 0;
181
182    /*
183     * Get memory or fail
184     */
185    if (NULL == (r = (pcrs_substitute *)malloc(sizeof(pcrs_substitute))))
186    {
187       *errptr = PCRS_ERR_NOMEM;
188       return NULL;
189    }
190    memset(r, '\0', sizeof(pcrs_substitute));
191
192    length = strlen(replacement);
193
194    if (NULL == (text = (char *)malloc(length + 1)))
195    {
196       free(r);
197       *errptr = PCRS_ERR_NOMEM;
198       return NULL;
199    }
200    memset(r, '\0', length + 1);
201    
202
203    /*
204     * In trivial mode, just copy the substitute text
205     */
206    if (trivialflag)
207    {
208       text = strncpy(text, replacement, length + 1);
209       k = length;
210    }
211
212    /*
213     * Else, parse, cut out and record all backreferences
214     */
215    else
216    {
217       while(i < length)
218       {
219          /* Quoting */
220          if (replacement[i] == '\\')
221          {
222             if (quoted)
223             {
224                text[k++] = replacement[i++];
225                quoted = 0;
226             }
227             else
228             {
229                quoted = 1;
230                i++;
231             }
232             continue;
233          }
234
235          /* Backreferences */
236          if (replacement[i] == '$' && !quoted && i < length - 1)
237          {
238             char *symbol, symbols[] = "'`+&";
239             r->block_length[l] = k - r->block_offset[l];
240
241             /* Numerical backreferences */
242             if (isdigit(replacement[i + 1]))
243             {
244                while (i < length && isdigit(replacement[++i]))
245                {
246                   r->backref[l] = r->backref[l] * 10 + replacement[i] - 48;
247                }
248             }
249
250             /* Symbolic backreferences: */
251             else if (NULL != (symbol = strchr(symbols, replacement[i + 1])))
252             {
253                
254                if (symbol - symbols == 2) /* $+ */
255                {
256                   r->backref[l] = capturecount;
257                }
258                else if (symbol - symbols == 3) /* $& */
259                {
260                   r->backref[l] = 0;
261                }
262                else /* $' or $` */
263                {
264                   r->backref[l] = PCRS_MAX_SUBMATCHES + 1 - (symbol - symbols);
265                }
266                i += 2;
267             }
268
269             /* Invalid backref -> plain '$' */
270             else
271             {
272                goto plainchar;
273             }
274
275             /* Valid and in range? -> record */
276             if (r->backref[l] < PCRS_MAX_SUBMATCHES + 2)
277             {
278                r->backref_count[r->backref[l]] += 1;
279                r->block_offset[++l] = k;
280             }
281             continue;
282          }
283          
284 plainchar:
285          /* Plain chars are copied */
286          text[k++] = replacement[i++];
287          quoted = 0;
288       }
289    } /* -END- if (!trivialflag) */
290
291    /*
292     * Finish & return
293     */
294    r->text = text;
295    r->backrefs = l;
296    r->block_length[l] = k - r->block_offset[l];
297
298    return r;
299
300 }
301
302
303 /*********************************************************************
304  *
305  * Function    :  pcrs_free_job
306  *
307  * Description :  Frees the memory used by a pcrs_job struct and its
308  *                dependant structures.
309  *
310  * Parameters  :
311  *          1  :  job = pointer to the pcrs_job structure to be freed
312  *
313  * Returns     :  a pointer to the next job, if there was any, or
314  *                NULL otherwise. 
315  *
316  *********************************************************************/
317 pcrs_job *pcrs_free_job(pcrs_job *job)
318 {
319    pcrs_job *next;
320
321    if (job == NULL)
322    {
323       return NULL;
324    }
325    else
326    {
327       next = job->next;
328       if (job->pattern != NULL) free(job->pattern);
329       if (job->hints != NULL) free(job->hints);
330       if (job->substitute != NULL)
331       {
332          if (job->substitute->text != NULL) free(job->substitute->text);
333          free(job->substitute);
334       }
335       free(job);
336    }
337    return next;
338
339 }
340
341
342 /*********************************************************************
343  *
344  * Function    :  pcrs_free_joblist
345  *
346  * Description :  Iterates through a chained list of pcrs_job's and
347  *                frees them using pcrs_free_job.
348  *
349  * Parameters  :
350  *          1  :  joblist = pointer to the first pcrs_job structure to
351  *                be freed
352  *
353  * Returns     :  N/A
354  *
355  *********************************************************************/
356 void pcrs_free_joblist(pcrs_job *joblist)
357 {
358    while ( NULL != (joblist = pcrs_free_job(joblist)) ) {};
359
360    return;
361
362 }
363
364
365 /*********************************************************************
366  *
367  * Function    :  pcrs_compile_command
368  *
369  * Description :  Parses a string with a Perl-style s/// command, 
370  *                calls pcrs_compile, and returns a corresponding
371  *                pcrs_job, or NULL if parsing or compiling the job
372  *                fails.
373  *
374  * Parameters  :
375  *          1  :  command = string with perl-style s/// command
376  *          2  :  errptr = pointer to an integer in which error
377  *                         conditions can be returned.
378  *
379  * Returns     :  a corresponding pcrs_job data structure, or NULL
380  *                if an error was encountered. In that case, *errptr
381  *                has the reason.
382  *
383  *********************************************************************/
384 pcrs_job *pcrs_compile_command(const char *command, int *errptr)
385 {
386    int i, k, l, limit, quoted = FALSE;
387    char delimiter;
388    char *tokens[4];   
389    pcrs_job *newjob;
390
391    i = k = l = 0;
392
393    /*
394     * Tokenize the perl command
395     */
396    limit = strlen(command);
397    if (limit < 4)
398    {
399       *errptr = PCRS_ERR_CMDSYNTAX;
400       return NULL;
401    }
402    else
403    {
404      delimiter = command[1];
405    }
406
407    tokens[l] = (char *) malloc(limit + 1);
408
409    for (i=0; i <= limit; i++)
410    {
411
412       if (command[i] == delimiter && !quoted)
413       {
414            if (l == 3)
415                 {
416                    l = -1;
417             break;
418          }
419          tokens[0][k++] = '\0';
420          tokens[++l] = tokens[0] + k;
421          continue;
422       }
423
424       else if (command[i] == '\\' && !quoted && i+1 < limit && command[i+1] == delimiter)
425       {
426          quoted = TRUE;
427          continue;
428       }
429       tokens[0][k++] = command[i];
430       quoted = FALSE;
431    }
432
433    /*
434     * Syntax error ?
435     */
436    if (l != 3)
437    {
438       *errptr = PCRS_ERR_CMDSYNTAX;
439       free(tokens[0]);
440       return NULL;
441    }
442
443    newjob = pcrs_compile(tokens[1], tokens[2], tokens[3], errptr);
444    free(tokens[0]);
445    return newjob;
446
447 }
448
449
450 /*********************************************************************
451  *
452  * Function    :  pcrs_compile
453  *
454  * Description :  Takes the three arguments to a perl s/// command
455  *                and compiles a pcrs_job structure from them.
456  *
457  * Parameters  :
458  *          1  :  pattern = string with perl-style pattern
459  *          2  :  substitute = string with perl-style substitute
460  *          3  :  options = string with perl-style options
461  *          4  :  errptr = pointer to an integer in which error
462  *                         conditions can be returned.
463  *
464  * Returns     :  a corresponding pcrs_job data structure, or NULL
465  *                if an error was encountered. In that case, *errptr
466  *                has the reason.
467  *
468  *********************************************************************/
469 pcrs_job *pcrs_compile(const char *pattern, const char *substitute, const char *options, int *errptr)
470 {
471    pcrs_job *newjob;
472    int flags;
473    int capturecount;
474    const char *error;
475
476
477    /* 
478     * Handle NULL arguments
479     */
480    if (pattern == NULL) pattern = "";
481    if (substitute == NULL) substitute = "";
482    if (options == NULL) options = "";
483
484
485    /* 
486     * Get and init memory
487     */
488    if (NULL == (newjob = (pcrs_job *)malloc(sizeof(pcrs_job))))
489    {
490       *errptr = PCRS_ERR_NOMEM;
491       return NULL;
492    }
493    memset(newjob, '\0', sizeof(pcrs_job));
494
495
496    /*
497     * Evaluate the options
498     */
499    newjob->options = pcrs_parse_perl_options(options, &flags);
500    newjob->flags = flags;
501
502
503    /*
504     * Compile the pattern
505     */
506    newjob->pattern = pcre_compile(pattern, newjob->options, &error, errptr, NULL);
507    if (newjob->pattern == NULL)
508    {
509       pcrs_free_job(newjob);
510       return NULL;
511    }
512
513
514    /*
515     * Generate hints. This has little overhead, since the
516     * hints will be NULL for a boring pattern anyway.
517     */
518    newjob->hints = pcre_study(newjob->pattern, 0, &error);
519    if (error != NULL)
520    {
521       *errptr = PCRS_ERR_STUDY;
522       pcrs_free_job(newjob);
523       return NULL;
524    }
525  
526
527    /* 
528     * Determine the number of capturing subpatterns. 
529     * This is needed for handling $+ in the substitute.
530     */
531    if (0 > (*errptr = pcre_fullinfo(newjob->pattern, newjob->hints, PCRE_INFO_CAPTURECOUNT, &capturecount)))
532    {
533       pcrs_free_job(newjob);
534       return NULL;
535    }
536  
537
538    /*
539     * Compile the substitute
540     */
541    if (NULL == (newjob->substitute = pcrs_compile_replacement(substitute, newjob->flags & PCRS_TRIVIAL, capturecount, errptr)))
542    {
543       pcrs_free_job(newjob);
544       return NULL;
545    }
546  
547    return newjob;
548
549 }
550
551
552 /*********************************************************************
553  *
554  * Function    :  pcrs_execute
555  *
556  * Description :  Modify the subject by executing the regular substitution
557  *                defined by the job. Since the result may be longer than
558  *                the subject, its space requirements are precalculated in
559  *                the matching phase and new memory is allocated accordingly.
560  *                It is the caller's responsibility to free the result when
561  *                it's no longer needed.
562  *
563  * Parameters  :
564  *          1  :  job = the pcrs_job to be executed
565  *          2  :  subject = the subject (== original) string
566  *          3  :  subject_length = the subject's length 
567  *                INCLUDING the terminating zero, if string!
568  *          4  :  result = char** for returning  the result 
569  *          5  :  result_length = int* for returning the result's length
570  *          6  :  max_matches = maximum number of matches in global searches
571  *
572  * Returns     :  the number of substitutions that were made. May be > 1
573  *                if job->flags contained PCRS_GLOBAL
574  *
575  *********************************************************************/
576 int pcrs_execute(pcrs_job *job, char *subject, int subject_length, char **result, int *result_length)
577 {
578    int offsets[3 * PCRS_MAX_SUBMATCHES],
579        offset, i, k,
580        matches_found,
581        newsize,
582        submatches,
583        max_matches = PCRS_MAX_MATCH_INIT;
584    pcrs_match *matches, *dummy;
585    char *result_offset;
586
587    offset = i = k = 0;
588
589    /* 
590     * Sanity check & memory allocation
591     */
592    if (job == NULL || job->pattern == NULL || job->substitute == NULL)
593    {
594       *result = NULL;
595       return(PCRS_ERR_BADJOB);
596    }
597
598    if (NULL == (matches = (pcrs_match *)malloc(max_matches * sizeof(pcrs_match))))
599    {
600       *result = NULL;
601       return(PCRS_ERR_NOMEM);
602    }
603    memset(matches, '\0', max_matches * sizeof(pcrs_match));
604
605
606    /*
607     * Find the pattern and calculate the space
608     * requirements for the result
609     */
610    newsize=subject_length;
611
612    while ((submatches = pcre_exec(job->pattern, job->hints, subject, subject_length, offset, 0, offsets, 3 * PCRS_MAX_SUBMATCHES)) > 0)
613    {
614       job->flags |= PCRS_SUCCESS;
615       matches[i].submatches = submatches;
616
617       for (k=0; k < submatches; k++)
618       {
619          matches[i].submatch_offset[k] = offsets[2 * k];
620
621          /* Note: Non-found optional submatches have length -1-(-1)==0 */
622          matches[i].submatch_length[k] = offsets[2 * k + 1] - offsets[2 * k]; 
623
624          /* reserve mem for each submatch as often as it is ref'd */
625          newsize += matches[i].submatch_length[k] * job->substitute->backref_count[k]; 
626       }
627       /* plus replacement text size minus match text size */
628       newsize += strlen(job->substitute->text) - matches[i].submatch_length[0]; 
629
630       /* chunk before match */
631       matches[i].submatch_offset[PCRS_MAX_SUBMATCHES] = 0;
632       matches[i].submatch_length[PCRS_MAX_SUBMATCHES] = offsets[0];
633       newsize += offsets[0] * job->substitute->backref_count[PCRS_MAX_SUBMATCHES];
634
635       /* chunk after match */
636       matches[i].submatch_offset[PCRS_MAX_SUBMATCHES + 1] = offsets[1];
637       matches[i].submatch_length[PCRS_MAX_SUBMATCHES + 1] = subject_length - offsets[1] - 1;
638       newsize += (subject_length - offsets[1])* job->substitute->backref_count[PCRS_MAX_SUBMATCHES + 1];
639
640       /* Non-global search or limit reached? */
641       if (!(job->flags & PCRS_GLOBAL)) break;
642
643       /* Storage for matches exhausted? -> Extend! */
644       if (++i >= max_matches)
645       {
646          max_matches *= PCRS_MAX_MATCH_GROW;
647          if (NULL == (dummy = (pcrs_match *)realloc(matches, max_matches * sizeof(pcrs_match))))
648          {
649             free(matches);
650             *result = NULL;
651             return(PCRS_ERR_NOMEM);
652          }
653          matches = dummy;
654       }
655
656       /* Don't loop on empty matches */
657       if (offsets[1] == offset)
658          if (offset < subject_length)
659             offset++;
660          else
661             break;
662       /* Go find the next one */
663       else
664          offset = offsets[1];
665    }
666    /* Pass pcre error through if failiure */
667    if (submatches < -1)
668    {
669       free(matches);
670       return submatches;   
671    }
672    matches_found = i;
673
674
675    /* 
676     * Get memory for the result
677     */
678    if ((*result = (char *)malloc(newsize)) == NULL)   /* must be free()d by caller */
679    {
680       free(matches);
681       return PCRS_ERR_NOMEM;
682    }
683
684
685    /* 
686     * Replace
687     */
688    offset = 0;
689    result_offset = *result;
690
691    for (i=0; i < matches_found; i++)
692    {
693       /* copy the chunk preceding the match */
694       memcpy(result_offset, subject + offset, matches[i].submatch_offset[0] - offset); 
695       result_offset += matches[i].submatch_offset[0] - offset;
696
697       /* For every segment of the substitute.. */
698       for (k=0; k <= job->substitute->backrefs; k++)
699       {
700          /* ...copy its text.. */
701          memcpy(result_offset, job->substitute->text + job->substitute->block_offset[k], job->substitute->block_length[k]);
702          result_offset += job->substitute->block_length[k];
703
704          /* ..plus, if it's not the last chunk, i.e.: There *is* a backref.. */
705          if (k != job->substitute->backrefs
706              /* ..in legal range.. */
707              && job->substitute->backref[k] < PCRS_MAX_SUBMATCHES + 2
708              /* ..and referencing a nonempty match.. */
709              && matches[i].submatch_length[job->substitute->backref[k]] > 0)
710          {
711             /* ..copy the submatch that is ref'd. */
712             memcpy(
713                result_offset,
714                subject + matches[i].submatch_offset[job->substitute->backref[k]],
715                matches[i].submatch_length[job->substitute->backref[k]]
716             );
717             result_offset += matches[i].submatch_length[job->substitute->backref[k]];
718          }
719       }
720       offset =  matches[i].submatch_offset[0] + matches[i].submatch_length[0];
721    }
722
723    /* Copy the rest. */
724    memcpy(result_offset, subject + offset, subject_length - offset);
725
726    *result_length = newsize;
727    free(matches);
728    return matches_found;
729
730 }
731
732
733 /*
734   Local Variables:
735   tab-width: 3
736   end:
737 */