Fixed bug: Empty substitutes now detected
[privoxy.git] / pcrs.c
1 const char pcrs_rcs[] = "$Id: pcrs.c,v 1.3 2001/05/25 11:03:55 oes Exp $";
2
3 /*********************************************************************
4  *
5  * File        :  $Source: /cvsroot/ijbswa/current/pcrs.c,v $
6  *
7  * Purpose     :  This is the alpha release of libpcrs. It is only published
8  *                at this early stage of development, because it is
9  *                needed for a new feature in JunkBuster.
10  *
11  *                While no inconsistencies, memory leaks or functional bugs
12  *                are known at this time, there *could* be plenty ;-). Also,
13  *                Many pcre-specific options are not yet supported, and
14  *                error handling needs improvement.
15  *
16  *                pcrs is a supplement to the brilliant pcre library by Philip
17  *                Hazel (ph10@cam.ac.uk) and adds Perl-style substitution. That
18  *                is, it mimics Perl's 's' operator.
19  *
20  *                Currently, there's no documentation besides comments and the
21  *                source itself ;-)
22  *
23  * Copyright   :  Written and Copyright (C) 2000 by Andreas Oesterhelt
24  *                <andreas@oesterhelt.org>
25  *
26  *                This program is free software; you can redistribute it 
27  *                and/or modify it under the terms of the GNU General
28  *                Public License as published by the Free Software
29  *                Foundation; either version 2 of the License, or (at
30  *                your option) any later version.
31  *
32  *                This program is distributed in the hope that it will
33  *                be useful, but WITHOUT ANY WARRANTY; without even the
34  *                implied warranty of MERCHANTABILITY or FITNESS FOR A
35  *                PARTICULAR PURPOSE.  See the GNU General Public
36  *                License for more details.
37  *
38  *                The GNU General Public License should be included with
39  *                this file.  If not, you can view it at
40  *                http://www.gnu.org/copyleft/gpl.html
41  *                or write to the Free Software Foundation, Inc., 59
42  *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
43  *
44  * Revisions   :
45  *    $Log: pcrs.c,v $
46  *    Revision 1.3  2001/05/25 11:03:55  oes
47  *    Added sanity check for NULL jobs to pcrs_exec_substitution
48  *
49  *    Revision 1.2  2001/05/22 18:46:04  oes
50  *
51  *    - Enabled filtering banners by size rather than URL
52  *      by adding patterns that replace all standard banner
53  *      sizes with the "Junkbuster" gif to the re_filterfile
54  *
55  *    - Enabled filtering WebBugs by providing a pattern
56  *      which kills all 1x1 images
57  *
58  *    - Added support for PCRE_UNGREEDY behaviour to pcrs,
59  *      which is selected by the (nonstandard and therefore
60  *      capital) letter 'U' in the option string.
61  *      It causes the quantifiers to be ungreedy by default.
62  *      Appending a ? turns back to greedy (!).
63  *
64  *    - Added a new interceptor ijb-send-banner, which
65  *      sends back the "Junkbuster" gif. Without imagelist or
66  *      MSIE detection support, or if tinygif = 1, or the
67  *      URL isn't recognized as an imageurl, a lame HTML
68  *      explanation is sent instead.
69  *
70  *    - Added new feature, which permits blocking remote
71  *      script redirects and firing back a local redirect
72  *      to the browser.
73  *      The feature is conditionally compiled, i.e. it
74  *      can be disabled with --disable-fast-redirects,
75  *      plus it must be activated by a "fast-redirects"
76  *      line in the config file, has its own log level
77  *      and of course wants to be displayed by show-proxy-args
78  *      Note: Boy, all the #ifdefs in 1001 locations and
79  *      all the fumbling with configure.in and acconfig.h
80  *      were *way* more work than the feature itself :-(
81  *
82  *    - Because a generic redirect template was needed for
83  *      this, tinygif = 3 now uses the same.
84  *
85  *    - Moved GIFs, and other static HTTP response templates
86  *      to project.h
87  *
88  *    - Some minor fixes
89  *
90  *    - Removed some >400 CRs again (Jon, you really worked
91  *      a lot! ;-)
92  *
93  *    Revision 1.1.1.1  2001/05/15 13:59:02  oes
94  *    Initial import of version 2.9.3 source tree
95  *
96  *
97  *********************************************************************/
98 \f
99
100 #include <pcre.h>
101 #include <string.h>
102 #include "pcrs.h"
103 const char pcrs_h_rcs[] = PCRS_H_VERSION;
104
105
106 /*********************************************************************
107  *
108  * Function    :  my_strsep
109  *
110  * Description :  Convenience function. It acts like strsep, except that
111  *                it respects quoting of the delimiter character with the
112  *                quote character. (And, of course, quoting the quote char
113  *                with itself.)  Called from `pcrs_make_job'.
114  *
115  * Parameters  :
116  *          1  :  token = current token
117  *          2  :  text = string to tokenize
118  *          3  :  delimiter = single character deliminter
119  *          4  :  quote_char = character to cause quoting
120  *
121  * Returns     :  -1 => failure, else the length of the token found.
122  *                In the latter case, *text is the token's start.
123  *
124  *********************************************************************/
125 int my_strsep(char *token, char **text, char delimiter, char quote_char)
126 {
127    int i, k=0, limit, quoted = FALSE;
128
129    limit = strlen(*text);
130    if ( 0 == limit )
131    {
132       return -1;
133    }
134
135    token[0] = '\0';
136
137    for (i=0; i < limit; i++)
138    {
139       if (text[0][i] == delimiter && !quoted)
140       {
141          *text += 1;
142          break;
143       }
144       else if (text[0][i] == quote_char && !quoted && i+1 < limit && text[0][i+1] == delimiter)
145       {
146          quoted = TRUE;
147          continue;
148       }
149       token[k++] = text[0][i];
150       quoted = FALSE;
151    }
152    token[k] = '\0';
153    *text += i;
154    return k;
155
156 }
157
158
159 /*********************************************************************
160  *
161  * Function    :  pcrs_compile_perl_options
162  *
163  * Description :  This function parses a string containing the options to
164  *                Perl's s/// operator. It returns an integer that is the
165  *                pcre equivalent of the symbolic optstring.
166  *                Since pcre doesn't know about Perl's 'g' (global) option,
167  *                but pcrs needs it, the globalflag integer is set if 'g'
168  *                is encountered.
169  *
170  * Parameters  :
171  *          1  :  optstring = string with options in perl syntax
172  *          2  :  globalflag = see description
173  *
174  * Returns     :  option integer suitable for pcre 
175  *
176  *********************************************************************/
177 int pcrs_compile_perl_options(char *optstring, int *globalflag)
178 {
179    int i, rc = 0;
180    *globalflag = 0;
181    for (i=0; i < strlen(optstring); i++)
182    {
183       switch(optstring[i])
184       {
185          case 'e': break;
186          case 'g': *globalflag = 1; break;
187          case 'i': rc |= PCRE_CASELESS; break;
188          case 'm': rc |= PCRE_MULTILINE; break;
189          case 'o': break;
190          case 's': rc |= PCRE_DOTALL; break;
191          case 'x': rc |= PCRE_EXTENDED; break;
192          case 'U': rc |= PCRE_UNGREEDY; break;
193          default:  break;
194       }
195    }
196    return rc;
197
198 }
199
200
201 /*********************************************************************
202  *
203  * Function    :  pcrs_compile_replacement
204  *
205  * Description :  This function takes a Perl-style replacement (2nd argument
206  *                to the s/// operator and returns a compiled pcrs_substitute,
207  *                or NULL if memory allocation for the substitute structure
208  *                fails.
209  *
210  * Parameters  :
211  *          1  :  replacement = replacement part of s/// operator
212  *                              in perl syntax
213  *          2  :  errptr = pointer to an integer in which error
214  *                         conditions can be returned.
215  *
216  * Returns     :  pcrs_substitute data structure, or NULL if an
217  *                error is encountered. In that case, *errptr has
218  *                the reason.
219  *
220  *********************************************************************/
221 pcrs_substitute *pcrs_compile_replacement(char *replacement, int *errptr)
222 {
223    int length, i, k = 0, l = 0, quoted = 0, idx;
224    char *text, *num_ptr, *numbers = "0123456789";
225    pcrs_substitute *r;
226
227    r = (pcrs_substitute *)malloc(sizeof(pcrs_substitute));
228    if (r == NULL) return NULL;
229    memset(r, '\0', sizeof(pcrs_substitute));
230
231    text = strdup(replacement);      /* must be free()d by caller */
232    if (text  == NULL)
233    {
234       *errptr = PCRS_ERR_NOMEM;
235       free(r);
236       return NULL;
237    }
238
239    length = strlen(replacement);
240
241    for (i=0; i < length; i++)
242    {
243       /* Backslash treatment */
244       if (replacement[i] == '\\')
245       {
246          if (quoted)
247          {
248             text[k++] = replacement[i];
249             quoted = 0;
250          }
251          else
252          {
253             quoted = 1;
254          }
255          continue;
256       }
257
258       /* Dollar treatment */
259       if (replacement[i] == '$' && !quoted && i < length - 1)
260       {
261          if (strchr("0123456789&", replacement[i + 1]) == NULL)
262          {
263             text[k++] = replacement[i];
264          }
265          else
266          {
267             r->block_length[l] = k - r->block_offset[l];
268             r->backref[l] = 0;
269             if (replacement[i + 1] != '&')
270             {
271                while ((num_ptr = strchr(numbers, replacement[++i])) != NULL && i < length)
272                {
273                   idx = num_ptr - numbers;
274                   r->backref[l] = r->backref[l] * 10 + idx;
275                }
276                i--;
277             }
278             else
279                i++;
280             if (r->backref[l] < PCRS_MAX_SUBMATCHES)
281                r->backref_count[r->backref[l]] += 1;
282             l++;
283             r->block_offset[l] = k;
284          }
285          continue;
286       }
287
288       /* Plain char treatment */
289       text[k++] = replacement[i];
290       quoted = 0;
291    }
292
293    text[k] = '\0';
294    r->text = text;
295    r->backrefs = l;
296    r->block_length[l] = k - r->block_offset[l];
297    return r;
298
299 }
300
301
302 /*********************************************************************
303  *
304  * Function    :  pcrs_free_job
305  *
306  * Description :  Frees the memory used by a pcrs_job struct and its
307  *                dependant structures. Returns a pointer to the next
308  *                job, if there was any, or NULL otherwise.
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_make_job
345  *
346  * Description :  Main entry point. Takes a string with a Perl-style
347  *                s/// command and returns a corresponding pcrs_job,
348  *                or NULL if compiling the job fails at any stage.
349  *                Diagnostics could obviously be improved.
350  *
351  * Parameters  :
352  *          1  :  command = string with perl-style s/// command
353  *          2  :  errptr = pointer to an integer in which error
354  *                         conditions can be returned.
355  *
356  * Returns     :  a corresponding pcrs_job data structure, or NULL
357  *                if an error was encountered. In that case, *errptr
358  *                has the reason.
359  *
360  *********************************************************************/
361 pcrs_job *pcrs_make_job(char *command, int *errptr)
362 {
363    char *dummy, *token, delimiter;
364    const char *error;
365    int i = 0, globalflag;
366    pcrs_job *newjob;
367
368    /* Get and init memory */
369    if ((newjob = (pcrs_job *)malloc(sizeof(pcrs_job))) == NULL)
370    {
371       *errptr = PCRS_ERR_NOMEM;
372       return NULL;
373    }
374    memset(newjob, '\0', sizeof(pcrs_job));
375
376    /* Command too short? */
377    if (strlen(command) < 4)
378    {
379       *errptr = PCRS_ERR_CMDSYNTAX;
380       pcrs_free_job(newjob);
381       return NULL;
382    }
383
384    /* Split command into tokens and handle them */
385    delimiter = command[1];
386    token = (char *)malloc(strlen(command)); /* current token */
387    dummy = (char *)malloc(strlen(command)); /* must store pattern, since we can't */
388                                             /* use it until the options are known */
389    while (my_strsep(token, &command, delimiter, '\\') >= 0)
390    {
391       switch (i)
392       {
393          /* We don't care about the command and assume 's' */
394          case 0:
395             break;
396
397          /* The pattern */
398          case 1:
399             strcpy(dummy, token);
400             break;
401
402          /* The substitute */
403          case 2:
404             if ((newjob->substitute = pcrs_compile_replacement(token, errptr)) == NULL)
405             {
406                pcrs_free_job(newjob);
407                return NULL;
408             }
409             else
410             {
411                break;
412             }
413
414          /* The options */
415          case 3:
416             newjob->options = pcrs_compile_perl_options(token, &globalflag);
417             newjob->globalflag = globalflag;
418             break;
419
420          /* There shouldn't be anything else! */
421          default:
422             *errptr = PCRS_ERR_CMDSYNTAX;
423             pcrs_free_job(newjob);
424             return NULL;
425       }
426       i++;
427    }
428    free(token);
429
430    /* We have a valid substitute? */
431    if (newjob->substitute == NULL)
432    {
433       *errptr = PCRS_ERR_CMDSYNTAX;
434       pcrs_free_job(newjob);
435       return NULL;
436    }
437
438    /* Compile the pattern */
439    newjob->pattern = pcre_compile(dummy, newjob->options, &error, errptr, NULL);
440    if (newjob->pattern == NULL)
441    {
442       pcrs_free_job(newjob);
443       return NULL;
444    }
445    free(dummy);
446
447    /*
448     * Generate hints. This has little overhead, since the
449     * hints will be NULL for a boring pattern anyway.
450     */
451    newjob->hints = pcre_study(newjob->pattern, 0, &error);
452    if (error != NULL)
453    {
454       *errptr = PCRS_ERR_STUDY;
455       pcrs_free_job(newjob);
456       return NULL;
457    }
458
459    return newjob;
460
461 }
462
463
464 /*********************************************************************
465  *
466  * Function    :  create_pcrs_job
467  *
468  * Description :  Create a job from all its components, if you don't
469  *                have a Perl command to start from. Rather boring.
470  *
471  * Parameters  :
472  *          1  :  pattern = pointer to pcre pattern
473  *          2  :  hints = pointer to pcre hints
474  *          3  :  options = options in pcre format
475  *          4  :  globalflag = flag that indicates if global matching is desired
476  *          5  :  substitute = pointer to pcrs_substitute data structure
477  *          2  :  errptr = pointer to an integer in which error
478  *                         conditions can be returned.
479  *
480  * Returns     :  pcrs_job structure, or NULL if an error was encountered.
481  *                In that case, *errptr has the reason why.
482  *
483  *********************************************************************/
484 pcrs_job *create_pcrs_job(pcre *pattern, pcre_extra *hints, int options, int globalflag, pcrs_substitute *substitute, int *errptr)
485 {
486    pcrs_job *newjob;
487
488    if ((newjob = (pcrs_job *)malloc(sizeof(pcrs_job))) == NULL)
489    {
490       *errptr = PCRS_ERR_NOMEM;
491       return NULL;
492    }
493    memset(newjob, '\0', sizeof(pcrs_job));
494
495    newjob->pattern = pattern;
496    newjob->hints = hints;
497    newjob->options = options;
498    newjob->globalflag = globalflag;
499    newjob->substitute = substitute;
500
501    return(newjob);
502
503 }
504
505
506 /*********************************************************************
507  *
508  * Function    :  pcrs_exec_substitution
509  *
510  * Description :  Modify the subject by executing the regular substitution
511  *                defined by the job. Since the result may be longer than
512  *                the subject, its space requirements are precalculated in
513  *                the matching phase and new memory is allocated accordingly.
514  *                It is the caller's responsibility to free the result when
515  *                it's no longer needed.
516  *
517  * Parameters  :
518  *          1  :  job = the pcrs_job to be executed
519  *          2  :  subject = the subject (== original) string
520  *          3  :  subject_length = the subject's length
521  *          4  :  result = char** for returning  the result 
522  *          5  :  result_length = int* for returning the result's length
523  *
524  * Returns     :  the number of substitutions that were made. May be > 1
525  *                if job->globalflag was set
526  *
527  *********************************************************************/
528 int pcrs_exec_substitution(pcrs_job *job, char *subject, int subject_length, char **result, int *result_length)
529 {
530    int offsets[3 * PCRS_MAX_SUBMATCHES],
531       offset = 0, i=0, k, matches_found, newsize, submatches;
532    pcrs_match matches[PCRS_MAX_MATCHES];
533    char *result_offset;
534
535    /* Sanity first */
536    if (job == NULL || job->pattern == NULL || job->substitute == NULL)
537    {
538       *result = NULL;
539       return(PCRS_ERR_BADJOB);
540    }
541
542    newsize=subject_length;
543
544    /* Find.. */
545    while ((submatches = pcre_exec(job->pattern, job->hints, subject, subject_length, offset, 0, offsets, 99)) > 0)
546    {
547       matches[i].submatches = submatches;
548       for (k=0; k < submatches; k++)
549       {
550          matches[i].submatch_offset[k] = offsets[2 * k];
551          matches[i].submatch_length[k] = offsets[2 * k + 1] - offsets[2 * k]; /* Non-found optional submatches have length -1-(-1)==0 */
552          newsize += matches[i].submatch_length[k] * job->substitute->backref_count[k]; /* reserve mem for each submatch as often as it is ref'd */
553       }
554       newsize += strlen(job->substitute->text) - matches[i].submatch_length[0]; /* plus replacement text size minus match text size */
555
556       /* Non-global search or limit reached? */
557       if (++i >= PCRS_MAX_MATCHES || !job->globalflag ) break;
558
559       /* Don't loop on empty matches */
560       if (offsets[1] == offset)
561          if (offset < subject_length)
562             offset++;
563          else
564             break;
565       /* Go find the next one */
566       else
567          offset = offsets[1];
568    }
569    if (submatches < -1) return submatches;   /* Pass pcre error through */
570    matches_found = i;
571
572    /* ..get memory ..*/
573    if ((*result = (char *)malloc(newsize)) == NULL)   /* must be free()d by caller */
574    {
575       return PCRS_ERR_NOMEM;
576    }
577
578    /* ..and replace */
579    offset = 0;
580    result_offset = *result;
581
582    for (i=0; i < matches_found; i++)
583    {
584       memcpy(result_offset, subject + offset, matches[i].submatch_offset[0] - offset); /* copy the chunk preceding the match */
585       result_offset += matches[i].submatch_offset[0] - offset;
586
587       /* For every segment of the substitute.. */
588       for (k=0; k <= job->substitute->backrefs; k++)
589       {
590          /* ...copy its text.. */
591          memcpy(result_offset, job->substitute->text + job->substitute->block_offset[k], job->substitute->block_length[k]);
592          result_offset += job->substitute->block_length[k];
593
594          /* ..plus, if it's not the last chunk (i.e.: There IS a backref).. */
595          if (k != job->substitute->backrefs
596              /* ..and a nonempty match.. */
597              && matches[i].submatch_length[job->substitute->backref[k]] > 0
598              /* ..and in legal range, ... */
599              && job->substitute->backref[k] <= PCRS_MAX_SUBMATCHES)
600          {
601             /* copy the submatch that is ref'd. */
602             memcpy(
603                result_offset,
604                subject + matches[i].submatch_offset[job->substitute->backref[k]],
605                matches[i].submatch_length[job->substitute->backref[k]]
606             );
607             result_offset += matches[i].submatch_length[job->substitute->backref[k]];
608          }
609       }
610       offset =  matches[i].submatch_offset[0] + matches[i].submatch_length[0];
611    }
612
613    /* Copy the rest. */
614    memcpy(result_offset, subject + offset, subject_length - offset);
615
616    *result_length = newsize;
617    return matches_found;
618
619 }
620
621
622 /*
623   Local Variables:
624   tab-width: 3
625   end:
626 */