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