Filename | /var/www/foswiki11/lib/Foswiki/Plugins/WysiwygPlugin/Handlers.pm |
Statements | Executed 62 statements in 5.87ms |
Calls | P | F | Exclusive Time |
Inclusive Time |
Subroutine |
---|---|---|---|---|---|
1 | 1 | 1 | 1.79ms | 7.31ms | BEGIN@18 | Foswiki::Plugins::WysiwygPlugin::Handlers::
4 | 1 | 1 | 47µs | 47µs | addXMLTag | Foswiki::Plugins::WysiwygPlugin::Handlers::
1 | 1 | 1 | 22µs | 54µs | BEGIN@7 | Foswiki::Plugins::WysiwygPlugin::Handlers::
1 | 1 | 1 | 22µs | 1.14ms | BEGIN@12 | Foswiki::Plugins::WysiwygPlugin::Handlers::
1 | 1 | 1 | 22µs | 28µs | BEGIN@693 | Foswiki::Plugins::WysiwygPlugin::Handlers::
1 | 1 | 1 | 19µs | 59µs | BEGIN@9 | Foswiki::Plugins::WysiwygPlugin::Handlers::
1 | 1 | 1 | 18µs | 50µs | BEGIN@278 | Foswiki::Plugins::WysiwygPlugin::Handlers::
1 | 1 | 1 | 14µs | 169µs | BEGIN@10 | Foswiki::Plugins::WysiwygPlugin::Handlers::
1 | 1 | 1 | 12µs | 19µs | BEGIN@8 | Foswiki::Plugins::WysiwygPlugin::Handlers::
1 | 1 | 1 | 7µs | 7µs | BEGIN@14 | Foswiki::Plugins::WysiwygPlugin::Handlers::
1 | 1 | 1 | 6µs | 6µs | BEGIN@16 | Foswiki::Plugins::WysiwygPlugin::Handlers::
1 | 1 | 1 | 5µs | 5µs | BEGIN@17 | Foswiki::Plugins::WysiwygPlugin::Handlers::
0 | 0 | 0 | 0s | 0s | RESTParameter2SiteCharSet | Foswiki::Plugins::WysiwygPlugin::Handlers::
0 | 0 | 0 | 0s | 0s | TranslateHTML2TML | Foswiki::Plugins::WysiwygPlugin::Handlers::
0 | 0 | 0 | 0s | 0s | TranslateTML2HTML | Foswiki::Plugins::WysiwygPlugin::Handlers::
0 | 0 | 0 | 0s | 0s | _JAVASCRIPT_TEXT | Foswiki::Plugins::WysiwygPlugin::Handlers::
0 | 0 | 0 | 0s | 0s | _OTOPICTAG | Foswiki::Plugins::WysiwygPlugin::Handlers::
0 | 0 | 0 | 0s | 0s | _OWEBTAG | Foswiki::Plugins::WysiwygPlugin::Handlers::
0 | 0 | 0 | 0s | 0s | _SECRET_ID | Foswiki::Plugins::WysiwygPlugin::Handlers::
0 | 0 | 0 | 0s | 0s | _WYSIWYG_TEXT | Foswiki::Plugins::WysiwygPlugin::Handlers::
0 | 0 | 0 | 0s | 0s | __ANON__[:481] | Foswiki::Plugins::WysiwygPlugin::Handlers::
0 | 0 | 0 | 0s | 0s | __ANON__[:872] | Foswiki::Plugins::WysiwygPlugin::Handlers::
0 | 0 | 0 | 0s | 0s | __ANON__[:875] | Foswiki::Plugins::WysiwygPlugin::Handlers::
0 | 0 | 0 | 0s | 0s | __ANON__[:878] | Foswiki::Plugins::WysiwygPlugin::Handlers::
0 | 0 | 0 | 0s | 0s | _convertImage | Foswiki::Plugins::WysiwygPlugin::Handlers::
0 | 0 | 0 | 0s | 0s | _dropBack | Foswiki::Plugins::WysiwygPlugin::Handlers::
0 | 0 | 0 | 0s | 0s | _liftOut | Foswiki::Plugins::WysiwygPlugin::Handlers::
0 | 0 | 0 | 0s | 0s | _populateVars | Foswiki::Plugins::WysiwygPlugin::Handlers::
0 | 0 | 0 | 0s | 0s | _restAttachments | Foswiki::Plugins::WysiwygPlugin::Handlers::
0 | 0 | 0 | 0s | 0s | _restHTML2TML | Foswiki::Plugins::WysiwygPlugin::Handlers::
0 | 0 | 0 | 0s | 0s | _restTML2HTML | Foswiki::Plugins::WysiwygPlugin::Handlers::
0 | 0 | 0 | 0s | 0s | _restUpload | Foswiki::Plugins::WysiwygPlugin::Handlers::
0 | 0 | 0 | 0s | 0s | _unquote | Foswiki::Plugins::WysiwygPlugin::Handlers::
0 | 0 | 0 | 0s | 0s | afterEditHandler | Foswiki::Plugins::WysiwygPlugin::Handlers::
0 | 0 | 0 | 0s | 0s | beforeCommonTagsHandler | Foswiki::Plugins::WysiwygPlugin::Handlers::
0 | 0 | 0 | 0s | 0s | beforeEditHandler | Foswiki::Plugins::WysiwygPlugin::Handlers::
0 | 0 | 0 | 0s | 0s | beforeMergeHandler | Foswiki::Plugins::WysiwygPlugin::Handlers::
0 | 0 | 0 | 0s | 0s | beforeSaveHandler | Foswiki::Plugins::WysiwygPlugin::Handlers::
0 | 0 | 0 | 0s | 0s | expandVarsInURL | Foswiki::Plugins::WysiwygPlugin::Handlers::
0 | 0 | 0 | 0s | 0s | getViewUrl | Foswiki::Plugins::WysiwygPlugin::Handlers::
0 | 0 | 0 | 0s | 0s | modifyHeaderHandler | Foswiki::Plugins::WysiwygPlugin::Handlers::
0 | 0 | 0 | 0s | 0s | postConvertURL | Foswiki::Plugins::WysiwygPlugin::Handlers::
0 | 0 | 0 | 0s | 0s | postRenderingHandler | Foswiki::Plugins::WysiwygPlugin::Handlers::
0 | 0 | 0 | 0s | 0s | protectedByAttr | Foswiki::Plugins::WysiwygPlugin::Handlers::
0 | 0 | 0 | 0s | 0s | returnRESTResult | Foswiki::Plugins::WysiwygPlugin::Handlers::
Line | State ments |
Time on line |
Calls | Time in subs |
Code |
---|---|---|---|---|---|
1 | # See bottom of file for license and copyright information | ||||
2 | package Foswiki::Plugins::WysiwygPlugin::Handlers; | ||||
3 | |||||
4 | # This package contains the handler functions used to implement the | ||||
5 | # WysiwygPlugin. They are implemented here so we can 'lazy-load' this | ||||
6 | # module only when it is actually required. | ||||
7 | 2 | 51µs | 2 | 86µs | # spent 54µs (22+32) within Foswiki::Plugins::WysiwygPlugin::Handlers::BEGIN@7 which was called:
# once (22µs+32µs) by Foswiki::Plugins::WysiwygPlugin::addXMLTag at line 7 # spent 54µs making 1 call to Foswiki::Plugins::WysiwygPlugin::Handlers::BEGIN@7
# spent 32µs making 1 call to strict::import |
8 | 2 | 45µs | 2 | 26µs | # spent 19µs (12+7) within Foswiki::Plugins::WysiwygPlugin::Handlers::BEGIN@8 which was called:
# once (12µs+7µs) by Foswiki::Plugins::WysiwygPlugin::addXMLTag at line 8 # spent 19µs making 1 call to Foswiki::Plugins::WysiwygPlugin::Handlers::BEGIN@8
# spent 7µs making 1 call to warnings::import |
9 | 2 | 50µs | 2 | 98µs | # spent 59µs (19+39) within Foswiki::Plugins::WysiwygPlugin::Handlers::BEGIN@9 which was called:
# once (19µs+39µs) by Foswiki::Plugins::WysiwygPlugin::addXMLTag at line 9 # spent 59µs making 1 call to Foswiki::Plugins::WysiwygPlugin::Handlers::BEGIN@9
# spent 39µs making 1 call to Assert::import |
10 | 2 | 54µs | 2 | 324µs | # spent 169µs (14+155) within Foswiki::Plugins::WysiwygPlugin::Handlers::BEGIN@10 which was called:
# once (14µs+155µs) by Foswiki::Plugins::WysiwygPlugin::addXMLTag at line 10 # spent 169µs making 1 call to Foswiki::Plugins::WysiwygPlugin::Handlers::BEGIN@10
# spent 155µs making 1 call to Error::import |
11 | |||||
12 | 2 | 61µs | 2 | 2.25ms | # spent 1.14ms (22µs+1.11) within Foswiki::Plugins::WysiwygPlugin::Handlers::BEGIN@12 which was called:
# once (22µs+1.11ms) by Foswiki::Plugins::WysiwygPlugin::addXMLTag at line 12 # spent 1.14ms making 1 call to Foswiki::Plugins::WysiwygPlugin::Handlers::BEGIN@12
# spent 1.11ms making 1 call to CGI::import |
13 | |||||
14 | 2 | 40µs | 1 | 7µs | # spent 7µs within Foswiki::Plugins::WysiwygPlugin::Handlers::BEGIN@14 which was called:
# once (7µs+0s) by Foswiki::Plugins::WysiwygPlugin::addXMLTag at line 14 # spent 7µs making 1 call to Foswiki::Plugins::WysiwygPlugin::Handlers::BEGIN@14 |
15 | |||||
16 | 2 | 29µs | 1 | 6µs | # spent 6µs within Foswiki::Plugins::WysiwygPlugin::Handlers::BEGIN@16 which was called:
# once (6µs+0s) by Foswiki::Plugins::WysiwygPlugin::addXMLTag at line 16 # spent 6µs making 1 call to Foswiki::Plugins::WysiwygPlugin::Handlers::BEGIN@16 |
17 | 2 | 33µs | 1 | 5µs | # spent 5µs within Foswiki::Plugins::WysiwygPlugin::Handlers::BEGIN@17 which was called:
# once (5µs+0s) by Foswiki::Plugins::WysiwygPlugin::addXMLTag at line 17 # spent 5µs making 1 call to Foswiki::Plugins::WysiwygPlugin::Handlers::BEGIN@17 |
18 | 2 | 1.80ms | 1 | 7.31ms | # spent 7.31ms (1.79+5.51) within Foswiki::Plugins::WysiwygPlugin::Handlers::BEGIN@18 which was called:
# once (1.79ms+5.51ms) by Foswiki::Plugins::WysiwygPlugin::addXMLTag at line 18 # spent 7.31ms making 1 call to Foswiki::Plugins::WysiwygPlugin::Handlers::BEGIN@18 |
19 | |||||
20 | 1 | 200ns | our $html2tml; | ||
21 | 1 | 100ns | our $imgMap; | ||
22 | 1 | 300ns | our @refs; | ||
23 | 1 | 200ns | our %xmltagPlugin; | ||
24 | |||||
25 | 1 | 1µs | our $SECRET_ID = | ||
26 | 'WYSIWYG content - do not remove this comment, and never use this identical text in your topics'; | ||||
27 | |||||
28 | sub _SECRET_ID { | ||||
29 | $SECRET_ID; | ||||
30 | } | ||||
31 | |||||
32 | sub _OWEBTAG { | ||||
33 | my ( $session, $params, $topic, $web ) = @_; | ||||
34 | |||||
35 | my $query = Foswiki::Func::getCgiQuery(); | ||||
36 | |||||
37 | return $web unless $query; | ||||
38 | |||||
39 | if ( defined( $query->param('templatetopic') ) ) { | ||||
40 | my @split = split( /\./, $query->param('templatetopic') ); | ||||
41 | |||||
42 | if ( $#split == 0 ) { | ||||
43 | return $web; | ||||
44 | } | ||||
45 | else { | ||||
46 | return $split[0]; | ||||
47 | } | ||||
48 | } | ||||
49 | |||||
50 | return $web; | ||||
51 | } | ||||
52 | |||||
53 | sub _OTOPICTAG { | ||||
54 | my ( $session, $params, $topic, $web ) = @_; | ||||
55 | |||||
56 | my $query = Foswiki::Func::getCgiQuery(); | ||||
57 | |||||
58 | return $topic unless $query; | ||||
59 | |||||
60 | if ( defined( $query->param('templatetopic') ) ) { | ||||
61 | my @split = split( /\./, $query->param('templatetopic') ); | ||||
62 | |||||
63 | return $split[$#split]; | ||||
64 | } | ||||
65 | |||||
66 | return $topic; | ||||
67 | } | ||||
68 | |||||
69 | # This handler is used to determine whether the topic is editable by | ||||
70 | # a WYSIWYG editor or not. The only thing it does is to redirect to a | ||||
71 | # normal edit url if the skin is set to WYSIWYGPLUGIN_WYSIWYGSKIN and | ||||
72 | # nasty content is found. | ||||
73 | sub beforeEditHandler { | ||||
74 | |||||
75 | #my( $text, $topic, $web, $meta ) = @_; | ||||
76 | |||||
77 | my $skin = Foswiki::Func::getPreferencesValue('WYSIWYGPLUGIN_WYSIWYGSKIN'); | ||||
78 | |||||
79 | if ( $skin && Foswiki::Func::getSkin() =~ /\b$skin\b/o ) { | ||||
80 | if ( notWysiwygEditable( $_[0] ) ) { | ||||
81 | |||||
82 | # redirect | ||||
83 | my $query = Foswiki::Func::getCgiQuery(); | ||||
84 | foreach my $p (qw( skin cover )) { | ||||
85 | my $arg = $query->param($p); | ||||
86 | if ( $arg && $arg =~ s/\b$skin\b// ) { | ||||
87 | if ( $arg =~ /^[\s,]*$/ ) { | ||||
88 | $query->delete($p); | ||||
89 | } | ||||
90 | else { | ||||
91 | $query->param( -name => $p, -value => $arg ); | ||||
92 | } | ||||
93 | } | ||||
94 | } | ||||
95 | my $url = $query->url( -full => 1, -path => 1, -query => 1 ); | ||||
96 | Foswiki::Func::redirectCgiQuery( $query, $url ); | ||||
97 | |||||
98 | # Bring this session to an untimely end | ||||
99 | exit 0; | ||||
100 | } | ||||
101 | } | ||||
102 | } | ||||
103 | |||||
104 | # This handler is only invoked *after* merging is complete | ||||
105 | sub beforeSaveHandler { | ||||
106 | |||||
107 | #my( $text, $topic, $web ) = @_; | ||||
108 | my $query = Foswiki::Func::getCgiQuery(); | ||||
109 | return unless $query; | ||||
110 | |||||
111 | return unless defined( $query->param('wysiwyg_edit') ); | ||||
112 | |||||
113 | $_[0] = TranslateHTML2TML( $_[0], $_[1], $_[2] ); | ||||
114 | } | ||||
115 | |||||
116 | # This handler is invoked before a merge. Merges are done before the | ||||
117 | # afterEditHandler is called, so we need to translate here. | ||||
118 | sub beforeMergeHandler { | ||||
119 | |||||
120 | #my( $text, $currRev, $currText, $origRev, $origText, $web, $topic ) = @_; | ||||
121 | afterEditHandler( $_[0], $_[6], $_[5] ); | ||||
122 | } | ||||
123 | |||||
124 | # This handler is invoked *after* a merge, and only from the edit | ||||
125 | # script (so it's useless for a REST save) | ||||
126 | sub afterEditHandler { | ||||
127 | my ( $text, $topic, $web ) = @_; | ||||
128 | my $query = Foswiki::Func::getCgiQuery(); | ||||
129 | return unless $query; | ||||
130 | |||||
131 | return | ||||
132 | unless defined( $query->param('wysiwyg_edit') ) | ||||
133 | || $text =~ s/<!--$SECRET_ID-->//go; | ||||
134 | |||||
135 | # Switch off wysiwyg_edit so it doesn't try to transform again in | ||||
136 | # the beforeSaveHandler | ||||
137 | $query->delete('wysiwyg_edit'); | ||||
138 | |||||
139 | $text = TranslateHTML2TML( $text, $_[1], $_[2] ); | ||||
140 | |||||
141 | $_[0] = $text; | ||||
142 | } | ||||
143 | |||||
144 | # Invoked to convert HTML to TML (best efforts) | ||||
145 | sub TranslateHTML2TML { | ||||
146 | my ( $text, $topic, $web ) = @_; | ||||
147 | |||||
148 | # $text must be in encoded in the site charset | ||||
149 | ASSERT( $text !~ /[^\x00-\xff]/, | ||||
150 | "only octets expected in input to TranslateHTML2TML" ) | ||||
151 | if DEBUG; | ||||
152 | |||||
153 | unless ($html2tml) { | ||||
154 | require Foswiki::Plugins::WysiwygPlugin::HTML2TML; | ||||
155 | |||||
156 | $html2tml = new Foswiki::Plugins::WysiwygPlugin::HTML2TML(); | ||||
157 | } | ||||
158 | |||||
159 | # SMELL: really, really bad smell; bloody core should NOT pass text | ||||
160 | # with embedded meta to plugins! It is VERY BAD DESIGN!!! | ||||
161 | my $top = ''; | ||||
162 | if ( $text =~ s/^(%META:[A-Z]+{.*?}%\r?\n)//s ) { | ||||
163 | $top = $1; | ||||
164 | } | ||||
165 | my $bottom = ''; | ||||
166 | $text =~ s/^(%META:[A-Z]+{.*?}%\r?\n)/$bottom = "$1$bottom";''/gem; | ||||
167 | |||||
168 | my $opts = { | ||||
169 | web => $web, | ||||
170 | topic => $topic, | ||||
171 | convertImage => \&_convertImage, | ||||
172 | rewriteURL => \&postConvertURL, | ||||
173 | very_clean => 1, # aggressively polish saved HTML | ||||
174 | }; | ||||
175 | |||||
176 | $text = $html2tml->convert( $text, $opts ); | ||||
177 | |||||
178 | $text =~ s/\s+$/\n/s; | ||||
179 | |||||
180 | ASSERT( $text !~ /[^\x00-\xff]/, | ||||
181 | "only octets expected in topic text output from TranslateHTML2TML" ) | ||||
182 | if DEBUG; | ||||
183 | return $top . $text . $bottom; | ||||
184 | } | ||||
185 | |||||
186 | # Handler used to process text in a =view= URL to generate text/html | ||||
187 | # containing the HTML of the topic to be edited. | ||||
188 | # | ||||
189 | # Invoked when the selected skin is in use to convert the text to HTML | ||||
190 | # We can't use the beforeEditHandler, because the editor loads up and then | ||||
191 | # uses a URL to fetch the text to be edited. This handler is designed to | ||||
192 | # provide the text for that request. It's a real struggle, because the | ||||
193 | # commonTagsHandler is called so many times that getting the right | ||||
194 | # call is hard, and then preventing a repeat call is harder! | ||||
195 | sub beforeCommonTagsHandler { | ||||
196 | |||||
197 | #my ( $text, $topic, $web, $meta ) | ||||
198 | my $query = Foswiki::Func::getCgiQuery(); | ||||
199 | |||||
200 | # stop it from processing the template without expanded | ||||
201 | # %TEXT% (grr; we need a better way to tell where we | ||||
202 | # are in the processing pipeline) | ||||
203 | return if ( $_[0] =~ /^<!-- WysiwygPlugin Template/ ); | ||||
204 | |||||
205 | # Have to re-read the topic because verbatim blocks have already been | ||||
206 | # lifted out, and we need them. | ||||
207 | my $topic = $_[1]; | ||||
208 | my $web = $_[2]; | ||||
209 | my ( $meta, $text ); | ||||
210 | my $altText = $query->param('templatetopic'); | ||||
211 | if ( $altText && Foswiki::Func::topicExists( $web, $altText ) ) { | ||||
212 | ( $web, $topic ) = | ||||
213 | Foswiki::Func::normalizeWebTopicName( $web, $altText ); | ||||
214 | } | ||||
215 | |||||
216 | $_[0] = _WYSIWYG_TEXT( $Foswiki::Plugins::SESSION, {}, $topic, $web ); | ||||
217 | } | ||||
218 | |||||
219 | # Handler used by editors that require pre-prepared HTML embedded in the | ||||
220 | # edit template. | ||||
221 | sub _WYSIWYG_TEXT { | ||||
222 | my ( $session, $params, $topic, $web ) = @_; | ||||
223 | |||||
224 | # Have to re-read the topic because content has already been munged | ||||
225 | # by other plugins, or by the extraction of verbatim blocks. | ||||
226 | my ( $meta, $text ) = Foswiki::Func::readTopic( $web, $topic ); | ||||
227 | |||||
228 | $text = TranslateTML2HTML( $text, $web, $topic ); | ||||
229 | |||||
230 | # Lift out the text to protect it from further Foswiki rendering. It will be | ||||
231 | # put back in the postRenderingHandler. | ||||
232 | return _liftOut($text); | ||||
233 | } | ||||
234 | |||||
235 | # Handler used to present the editable text in a javascript constant string | ||||
236 | sub _JAVASCRIPT_TEXT { | ||||
237 | my ( $session, $params, $topic, $web ) = @_; | ||||
238 | |||||
239 | my $html = _dropBack( _WYSIWYG_TEXT(@_) ); | ||||
240 | |||||
241 | $html =~ s/([\\'])/\\$1/sg; | ||||
242 | $html =~ s/\r/\\r/sg; | ||||
243 | $html =~ s/\n/\\n/sg; | ||||
244 | $html =~ s/script/scri'+'pt/g; | ||||
245 | |||||
246 | return _liftOut("'$html'"); | ||||
247 | } | ||||
248 | |||||
249 | sub postRenderingHandler { | ||||
250 | |||||
251 | # Replace protected content. | ||||
252 | $_[0] = _dropBack( $_[0] ); | ||||
253 | } | ||||
254 | |||||
255 | sub modifyHeaderHandler { | ||||
256 | my ( $headers, $query ) = @_; | ||||
257 | |||||
258 | if ( $query->param('wysiwyg_edit') ) { | ||||
259 | $headers->{Expires} = 0; | ||||
260 | $headers->{'Cache-control'} = 'max-age=0, must-revalidate'; | ||||
261 | } | ||||
262 | } | ||||
263 | |||||
264 | # callback passed to the TML2HTML convertor | ||||
265 | sub getViewUrl { | ||||
266 | my ( $web, $topic ) = @_; | ||||
267 | |||||
268 | # the Cairo documentation says getViewUrl defaults the web. It doesn't. | ||||
269 | unless ( defined $Foswiki::Plugins::SESSION ) { | ||||
270 | $web ||= $Foswiki::webName; | ||||
271 | } | ||||
272 | |||||
273 | return Foswiki::Func::getViewUrl( $web, $topic ); | ||||
274 | } | ||||
275 | |||||
276 | # The subset of vars for which bidirection transformation is supported | ||||
277 | # in URLs only | ||||
278 | 2 | 2.04ms | 2 | 82µs | # spent 50µs (18+32) within Foswiki::Plugins::WysiwygPlugin::Handlers::BEGIN@278 which was called:
# once (18µs+32µs) by Foswiki::Plugins::WysiwygPlugin::addXMLTag at line 278 # spent 50µs making 1 call to Foswiki::Plugins::WysiwygPlugin::Handlers::BEGIN@278
# spent 32µs making 1 call to vars::import |
279 | |||||
280 | # The set of macros that get "special treatment" in URLs | ||||
281 | 1 | 3µs | @VARS = ( | ||
282 | '%ATTACHURL%', | ||||
283 | '%ATTACHURLPATH%', | ||||
284 | '%PUBURL%', | ||||
285 | '%PUBURLPATH%', | ||||
286 | '%SCRIPTURLPATH{"view"}%', | ||||
287 | '%SCRIPTURLPATH%', | ||||
288 | '%SCRIPTURL{"view"}%', | ||||
289 | '%SCRIPTURL%', | ||||
290 | '%SCRIPTSUFFIX%', # bit dodgy, this one | ||||
291 | ); | ||||
292 | |||||
293 | # Initialises the mapping from var to URL and back | ||||
294 | sub _populateVars { | ||||
295 | my $opts = shift; | ||||
296 | |||||
297 | return if ( $opts->{exp} ); | ||||
298 | |||||
299 | local $Foswiki::Plugins::WysiwygPlugin::recursionBlock = | ||||
300 | 1; # block calls to beforeCommonTagshandler | ||||
301 | |||||
302 | # Item9973: We call CGI::unescape() on the expanded value, because the | ||||
303 | # content of the src attribute on an img tag is received from WYSIWYG that | ||||
304 | # way. Without this expandVarsInURL can only match webs, topics, attachments | ||||
305 | # named with ascii characters (international characters fail match). | ||||
306 | my @exp = split( | ||||
307 | /\0/, | ||||
308 | CGI::unescape( | ||||
309 | Foswiki::Func::expandCommonVariables( | ||||
310 | join( "\0", @VARS ), | ||||
311 | $opts->{topic}, $opts->{web} | ||||
312 | ) | ||||
313 | ) | ||||
314 | ); | ||||
315 | |||||
316 | for my $i ( 0 .. $#VARS ) { | ||||
317 | my $nvar = $VARS[$i]; | ||||
318 | $opts->{match}[$i] = $nvar; | ||||
319 | $exp[$i] ||= ''; | ||||
320 | } | ||||
321 | $opts->{exp} = \@exp; | ||||
322 | } | ||||
323 | |||||
324 | # callback passed to the TML2HTML convertor on each | ||||
325 | # variable in a URL used in a square bracketed link | ||||
326 | sub expandVarsInURL { | ||||
327 | my ( $url, $opts ) = @_; | ||||
328 | |||||
329 | return '' unless $url; | ||||
330 | |||||
331 | _populateVars($opts); | ||||
332 | for my $i ( 0 .. $#VARS ) { | ||||
333 | $url =~ s/$opts->{match}[$i]/$opts->{exp}->[$i]/g; | ||||
334 | } | ||||
335 | return $url; | ||||
336 | } | ||||
337 | |||||
338 | # callback passed to the HTML2TML convertor | ||||
339 | sub postConvertURL { | ||||
340 | my ( $url, $opts ) = @_; | ||||
341 | |||||
342 | #my $orig = $url; #debug | ||||
343 | |||||
344 | local $Foswiki::Plugins::WysiwygPlugin::recursionBlock = | ||||
345 | 1; # block calls to beforeCommonTagshandler | ||||
346 | |||||
347 | my $anchor = ''; | ||||
348 | if ( $url =~ s/(#.*)$// ) { | ||||
349 | $anchor = $1; | ||||
350 | } | ||||
351 | my $parameters = ''; | ||||
352 | if ( $url =~ s/(\?.*)$// ) { | ||||
353 | $parameters = $1; | ||||
354 | } | ||||
355 | |||||
356 | _populateVars($opts); | ||||
357 | |||||
358 | for my $i ( 0 .. $#VARS ) { | ||||
359 | next unless $opts->{exp}->[$i]; | ||||
360 | $url =~ s/^$opts->{exp}->[$i]/$VARS[$i]/; | ||||
361 | } | ||||
362 | |||||
363 | if ( $url =~ m#^%SCRIPTURL(?:PATH)?(?:{"view"}%|%/+view[^/]*)/+([/\w.]+)$# | ||||
364 | && !$parameters ) | ||||
365 | { | ||||
366 | my $orig = $1; | ||||
367 | my ( $web, $topic ) = | ||||
368 | Foswiki::Func::normalizeWebTopicName( $opts->{web}, $orig ); | ||||
369 | |||||
370 | if ( $web && $web ne $opts->{web} ) { | ||||
371 | |||||
372 | #print STDERR "$orig -> $web+$topic$anchor\n"; #debug | ||||
373 | return $web . '.' . $topic . $anchor; | ||||
374 | } | ||||
375 | |||||
376 | #print STDERR "$orig -> $topic$anchor\n"; #debug | ||||
377 | return $topic . $anchor; | ||||
378 | } | ||||
379 | |||||
380 | #print STDERR "$orig -> $url$anchor$parameters\n"; #debug | ||||
381 | return $url . $anchor . $parameters; | ||||
382 | } | ||||
383 | |||||
384 | # Callback used to convert an image reference into a Foswiki variable. | ||||
385 | sub _convertImage { | ||||
386 | my ( $src, $opts ) = @_; | ||||
387 | |||||
388 | return unless $src; | ||||
389 | |||||
390 | # block calls to beforeCommonTagshandler | ||||
391 | local $Foswiki::Plugins::WysiwygPlugin::recursionBlock = 1; | ||||
392 | |||||
393 | unless ($imgMap) { | ||||
394 | $imgMap = {}; | ||||
395 | my $imgs = Foswiki::Func::getPreferencesValue('WYSIWYGPLUGIN_ICONS'); | ||||
396 | if ($imgs) { | ||||
397 | while ( $imgs =~ s/src="(.*?)" alt="(.*?)"// ) { | ||||
398 | my ( $src, $alt ) = ( $1, $2 ); | ||||
399 | $src = | ||||
400 | Foswiki::Func::expandCommonVariables( $src, $opts->{topic}, | ||||
401 | $opts->{web} ); | ||||
402 | $alt .= '%' if $alt =~ /^%/; | ||||
403 | $imgMap->{$src} = $alt; | ||||
404 | } | ||||
405 | } | ||||
406 | } | ||||
407 | |||||
408 | return $imgMap->{$src}; | ||||
409 | } | ||||
410 | |||||
411 | # Replace content with a marker to prevent it being munged by Foswiki | ||||
412 | sub _liftOut { | ||||
413 | my ($text) = @_; | ||||
414 | my $n = scalar(@refs); | ||||
415 | push( @refs, $text ); | ||||
416 | return "\05$n\05"; | ||||
417 | } | ||||
418 | |||||
419 | # Substitute marker | ||||
420 | sub _dropBack { | ||||
421 | my ($text) = @_; | ||||
422 | |||||
423 | # Restore everything that was lifted out | ||||
424 | while ( $text =~ s/\05([0-9]+)\05/$refs[$1]/gi ) { | ||||
425 | } | ||||
426 | return $text; | ||||
427 | } | ||||
428 | |||||
429 | =begin TML | ||||
430 | |||||
431 | ---++ StaticMethod addXMLTag($tag, \&fn) | ||||
432 | |||||
433 | Instruct WysiwygPlugin to "lift out" the named tag | ||||
434 | and pass it to &fn for processing. | ||||
435 | &fn may modify the text of the tag. | ||||
436 | &fn should return 0 if the tag is to be re-embedded immediately, | ||||
437 | or 1 if it is to be re-embedded after all processing is complete. | ||||
438 | The text passed (by reference) to &fn includes the | ||||
439 | =<tag> ... </tag>= brackets. | ||||
440 | |||||
441 | The simplest use of this function is something like this: | ||||
442 | =Foswiki::Plugins::WysiwygPlugin::addXMLTag( 'mytag', sub { 1 } );= | ||||
443 | |||||
444 | A plugin may call this function more than once | ||||
445 | e.g. to change the processing function for a tag. | ||||
446 | However, only the *original plugin* may change the processing | ||||
447 | for a tag. | ||||
448 | |||||
449 | Plugins should call this function from their =initPlugin= | ||||
450 | handlers so that WysiwygPlugin will protect the XML-like tags | ||||
451 | for all conversions, including REST conversions. | ||||
452 | Plugins that are intended to be used with older versions of Foswiki | ||||
453 | (e.g. 1.0.6) should check that this function is defined before calling it, | ||||
454 | so that they degrade gracefully if an older version of WysiwygPlugin | ||||
455 | (e.g. that shipped with 1.0.6) is installed. | ||||
456 | |||||
457 | =cut | ||||
458 | |||||
459 | # spent 47µs within Foswiki::Plugins::WysiwygPlugin::Handlers::addXMLTag which was called 4 times, avg 12µs/call:
# 4 times (47µs+0s) by Foswiki::Plugins::WysiwygPlugin::addXMLTag at line 240 of /var/www/foswiki11/lib/Foswiki/Plugins/WysiwygPlugin.pm, avg 12µs/call | ||||
460 | 4 | 3µs | my ( $tag, $fn ) = @_; | ||
461 | |||||
462 | 4 | 16µs | my $plugin = caller; | ||
463 | 4 | 8µs | $plugin =~ s/^Foswiki::Plugins:://; | ||
464 | |||||
465 | 4 | 1µs | return if not defined $tag; | ||
466 | |||||
467 | 4 | 22µs | if ( | ||
468 | ( | ||||
469 | not exists $Foswiki::Plugins::WysiwygPlugin::xmltag{$tag} | ||||
470 | and not exists $xmltagPlugin{$tag} | ||||
471 | ) | ||||
472 | or ( $xmltagPlugin{$tag} eq $plugin ) | ||||
473 | ) | ||||
474 | { | ||||
475 | |||||
476 | # This is either a plugin adding a new tag | ||||
477 | # or a plugin adding a tag it had previously added before. | ||||
478 | # A plugin is allowed to add a tag that it had added before | ||||
479 | # and the new function replaces the old. | ||||
480 | # | ||||
481 | $fn = sub { 1 } | ||||
482 | 4 | 1µs | unless $fn; # Default function | ||
483 | |||||
484 | 4 | 4µs | $Foswiki::Plugins::WysiwygPlugin::xmltag{$tag} = $fn; | ||
485 | 4 | 3µs | $xmltagPlugin{$tag} = $plugin; | ||
486 | } | ||||
487 | else { | ||||
488 | |||||
489 | # DON'T replace the existing processing for this tag | ||||
490 | printf STDERR "WysiwygPlugin::addXMLTag: " | ||||
491 | . "$plugin cannot add XML tag $tag, " | ||||
492 | . "that tag was already registered by $xmltagPlugin{$tag}\n"; | ||||
493 | } | ||||
494 | } | ||||
495 | |||||
496 | sub TranslateTML2HTML { | ||||
497 | my ( $text, $web, $topic, @extraConvertOptions ) = @_; | ||||
498 | |||||
499 | ASSERT( $text !~ /[^\x00-\xff]/, | ||||
500 | "only octets expected in input to TranslateTML2HTML" ) | ||||
501 | if DEBUG; | ||||
502 | |||||
503 | # Translate the topic text to pure HTML. | ||||
504 | unless ($Foswiki::Plugins::WysiwygPlugin::tml2html) { | ||||
505 | require Foswiki::Plugins::WysiwygPlugin::TML2HTML; | ||||
506 | $Foswiki::Plugins::WysiwygPlugin::tml2html = | ||||
507 | new Foswiki::Plugins::WysiwygPlugin::TML2HTML(); | ||||
508 | } | ||||
509 | my $html = $Foswiki::Plugins::WysiwygPlugin::tml2html->convert( | ||||
510 | $_[0], | ||||
511 | { | ||||
512 | web => $web, | ||||
513 | topic => $topic, | ||||
514 | getViewUrl => \&getViewUrl, | ||||
515 | expandVarsInURL => \&expandVarsInURL, | ||||
516 | xmltag => \%Foswiki::Plugins::WysiwygPlugin::xmltag, | ||||
517 | @extraConvertOptions, | ||||
518 | } | ||||
519 | ); | ||||
520 | ASSERT( $html !~ /[^\x00-\xff]/, | ||||
521 | "only octets expected in output from TranslateTML2HTML" ) | ||||
522 | if DEBUG; | ||||
523 | return $html; | ||||
524 | } | ||||
525 | |||||
526 | # PACKAGE PRIVATE | ||||
527 | # Determine if sticky attributes prevent a tag being converted to | ||||
528 | # TML when this attribute is present. | ||||
529 | 1 | 300ns | my @protectedByAttr; | ||
530 | |||||
531 | sub protectedByAttr { | ||||
532 | my ( $tag, $attr ) = @_; | ||||
533 | |||||
534 | unless ( scalar(@protectedByAttr) ) { | ||||
535 | |||||
536 | # See the WysiwygPluginSettings for information on stickybits | ||||
537 | my $protection = | ||||
538 | Foswiki::Func::getPreferencesValue('WYSIWYGPLUGIN_STICKYBITS') | ||||
539 | || <<'DEFAULT'; | ||||
540 | (?!img).*=id,lang,title,dir,on.*; | ||||
541 | a=accesskey,coords,shape,target; | ||||
542 | bdo=dir; | ||||
543 | br=clear; | ||||
544 | col=char,charoff,span,valign,width; | ||||
545 | colgroup=align,char,charoff,span,valign,width; | ||||
546 | dir=compact; | ||||
547 | div=align,style; | ||||
548 | dl=compact; | ||||
549 | font=size,face; | ||||
550 | h\d=align; | ||||
551 | hr=align,noshade,size,width; | ||||
552 | legend=accesskey,align; | ||||
553 | li=value; | ||||
554 | ol=compact,start,type; | ||||
555 | p=align; | ||||
556 | param=name,type,value,valuetype; | ||||
557 | pre=width; | ||||
558 | q=cite; | ||||
559 | table=align,bgcolor,.*?background-color:.*,frame,rules,summary,width; | ||||
560 | tbody=align,char,charoff,valign; | ||||
561 | td=abbr,align,axis,bgcolor,.*?background-color:.*,.*?border-color:.*,char,charoff,headers,height,nowrap,rowspan,scope,valign,width; | ||||
562 | tfoot=align,char,charoff,valign; | ||||
563 | th=abbr,align,axis,bgcolor,.*?background-color:.*,char,charoff,height,nowrap,rowspan,scope,valign,width,headers; | ||||
564 | thead=align,char,charoff,valign; | ||||
565 | tr=bgcolor,.*?background-color:.*,char,charoff,valign; | ||||
566 | ul=compact,type; | ||||
567 | DEFAULT | ||||
568 | foreach my $def ( split( /;\s*/s, $protection ) ) { | ||||
569 | my ( $re, $ats ) = split( /\s*=\s*/s, $def, 2 ); | ||||
570 | push( | ||||
571 | @protectedByAttr, | ||||
572 | { | ||||
573 | tag => qr/$re/i, | ||||
574 | attrs => join( '|', split( /\s*,\s*/, $ats ) ) | ||||
575 | } | ||||
576 | ); | ||||
577 | } | ||||
578 | } | ||||
579 | foreach my $row (@protectedByAttr) { | ||||
580 | if ( $tag =~ /^$row->{tag}$/i ) { | ||||
581 | |||||
582 | #print STDERR | ||||
583 | # "Matched $tag, looking for ^($row->{attrs})\$ in $attr\n"; | ||||
584 | if ( $attr =~ /^($row->{attrs})$/i ) { | ||||
585 | |||||
586 | # print STDERR | ||||
587 | # "Protecting $tag with $attr matches $row->{attrs} \n"; #debug | ||||
588 | return 1; | ||||
589 | } | ||||
590 | } | ||||
591 | } | ||||
592 | return 0; | ||||
593 | } | ||||
594 | |||||
595 | # Text that is taken from a web page and added to the parameters of an XHR | ||||
596 | # by JavaScript is UTF-8 encoded. This is because UTF-8 is the default encoding | ||||
597 | # for XML, which XHR was designed to transport. | ||||
598 | |||||
599 | # This function is used to decode such parameters to the currently selected | ||||
600 | # Foswiki site character set. | ||||
601 | |||||
602 | # Note that this transform is not as simple as an Encode::from_to, as | ||||
603 | # a number of unicode code points must be remapped for certain encodings. | ||||
604 | sub RESTParameter2SiteCharSet { | ||||
605 | my ($text) = @_; | ||||
606 | |||||
607 | #print STDERR "octets in [". WC::debugEncode($text). "]\n\n"; | ||||
608 | # $text is supposed to contain octets that are valid UTF-8. | ||||
609 | # $text should certainly not have any codes above 255. | ||||
610 | ASSERT( $text !~ /[^\x00-\xff]/, | ||||
611 | "only octets expected in input to RESTParameter2SiteCharSet" ) | ||||
612 | if DEBUG; | ||||
613 | |||||
614 | # $text might contain octets that are not valid UTF-8 | ||||
615 | # because it came from the browser, and so it might be hostile content. | ||||
616 | # Encode::FB_PERLQQ makes decode_utf8 convert invalid octet sequences | ||||
617 | # into a perl escape sequence, octet for octet (e.g. \xFF\x80), | ||||
618 | # instead of throwing an exception. This defuses the invalid sequence. | ||||
619 | $text = Encode::decode_utf8( $text, Encode::FB_PERLQQ ); | ||||
620 | |||||
621 | # $text now contains unicode characters | ||||
622 | #print STDERR "as utf-8 [". WC::debugEncode($text). "]\n\n"; | ||||
623 | |||||
624 | if ( WC::encoding() =~ /^utf-?8/ ) { | ||||
625 | $text = Encode::encode_utf8($text); | ||||
626 | } | ||||
627 | else { | ||||
628 | |||||
629 | # The site charset is a non-UTF-8 8-bit charset | ||||
630 | |||||
631 | WC::convertNotRepresentabletoEntity($text); | ||||
632 | |||||
633 | # All characters that cannot be represented in the site charset are now encoded as entities | ||||
634 | # Named entities are used if available, otherwise numeric entities, | ||||
635 | # because named entities produce more readable TML | ||||
636 | |||||
637 | # Encode $text in the site charset | ||||
638 | # The Encode::FB_HTMLCREF should not be needed, as all characters in $text | ||||
639 | # are supposed to be representable in the site charset. | ||||
640 | $text = Encode::encode( WC::encoding(), $text, Encode::FB_HTMLCREF ); | ||||
641 | } | ||||
642 | |||||
643 | # $text is now encoded as per the site charset. | ||||
644 | # For UTF-8 - that means octets. | ||||
645 | # For non-UTF8, Unicode characters that cannot be represented in the site charset | ||||
646 | # are converted to HTML entities (preferring named entities to numeric entities) | ||||
647 | |||||
648 | # The return value is supposed to be according to the currently selected | ||||
649 | # Foswiki site character set, encoded as octets. | ||||
650 | # Thus, there should not be any codes above 255. | ||||
651 | ASSERT( $text !~ /[^\x00-\xff]/, | ||||
652 | "only octets expected in return value for RESTParameter2SiteCharSet" ) | ||||
653 | if DEBUG; | ||||
654 | |||||
655 | #print STDERR "octets out [". WC::debugEncode($text). "]\n\n"; | ||||
656 | return $text; | ||||
657 | } | ||||
658 | |||||
659 | # Text that is taken from a web page and added to the parameters of an XHR | ||||
660 | # by JavaScript is UTF-8 encoded. This is because UTF-8 is the default encoding | ||||
661 | # for XML, which XHR was designed to transport. For usefulness in Javascript | ||||
662 | # the response to an XHR should also be UTF-8 encoded. | ||||
663 | # This function generates such a response. | ||||
664 | sub returnRESTResult { | ||||
665 | my ( $response, $status, $text ) = @_; | ||||
666 | ASSERT( $text !~ /[^\x00-\xff]/, | ||||
667 | "only octets expected in input to returnRESTResult" ) | ||||
668 | if DEBUG; | ||||
669 | |||||
670 | $text = Encode::decode( WC::encoding(), $text, Encode::FB_HTMLCREF ); | ||||
671 | |||||
672 | #print STDERR "unicodechr[". WC::debugEncode($text). "]\n\n"; | ||||
673 | |||||
674 | $text = Encode::encode_utf8($text); | ||||
675 | |||||
676 | # Foswiki 1.0 introduces the Foswiki::Response object, which handles all | ||||
677 | # responses. | ||||
678 | if ( UNIVERSAL::isa( $response, 'Foswiki::Response' ) ) { | ||||
679 | $response->header( | ||||
680 | -status => $status, | ||||
681 | -type => 'text/plain', | ||||
682 | -charset => 'UTF-8' | ||||
683 | ); | ||||
684 | $response->print($text); | ||||
685 | } | ||||
686 | else { # Pre-Foswiki-1.0. | ||||
687 | # Turn off AUTOFLUSH | ||||
688 | # See http://perl.apache.org/docs/2.0/user/coding/coding.html | ||||
689 | local $| = 0; | ||||
690 | my $query = Foswiki::Func::getCgiQuery(); | ||||
691 | if ( defined($query) ) { | ||||
692 | my $len; | ||||
693 | 2 | 1.59ms | 2 | 34µs | # spent 28µs (22+6) within Foswiki::Plugins::WysiwygPlugin::Handlers::BEGIN@693 which was called:
# once (22µs+6µs) by Foswiki::Plugins::WysiwygPlugin::addXMLTag at line 693 # spent 28µs making 1 call to Foswiki::Plugins::WysiwygPlugin::Handlers::BEGIN@693
# spent 6µs making 1 call to bytes::import |
694 | print $query->header( | ||||
695 | -status => $status, | ||||
696 | -type => 'text/plain', | ||||
697 | -charset => 'UTF-8', | ||||
698 | -Content_length => $len | ||||
699 | ); | ||||
700 | print $text; | ||||
701 | } | ||||
702 | } | ||||
703 | print STDERR $text if ( $status >= 400 ); | ||||
704 | } | ||||
705 | |||||
706 | # Rest handler for use from Javascript. The 'text' parameter is used to | ||||
707 | # pass the text for conversion. The text must be URI-encoded (this is | ||||
708 | # to support use of this handler from XMLHttpRequest, which gets it | ||||
709 | # wrong). Example: | ||||
710 | # | ||||
711 | # var req = new XMLHttpRequest(); | ||||
712 | # req.open("POST", url, true); | ||||
713 | # req.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); | ||||
714 | # var params = "text=" + encodeURIComponent(escape(text)); | ||||
715 | # request.req.setRequestHeader("Content-length", params.length); | ||||
716 | # request.req.setRequestHeader("Connection", "close"); | ||||
717 | # request.req.onreadystatechange = ...; | ||||
718 | # req.send(params); | ||||
719 | # | ||||
720 | sub _restTML2HTML { | ||||
721 | my ( $session, $plugin, $verb, $response ) = @_; | ||||
722 | my $tml = Foswiki::Func::getCgiQuery()->param('text'); | ||||
723 | |||||
724 | $tml = RESTParameter2SiteCharSet($tml); | ||||
725 | |||||
726 | # if the secret ID is present, don't convert again. We are probably | ||||
727 | # going 'back' to this page (doesn't work on IE :-( ) | ||||
728 | if ( $tml =~ /<!--$SECRET_ID-->/ ) { | ||||
729 | return $tml; | ||||
730 | } | ||||
731 | |||||
732 | my $html = | ||||
733 | TranslateTML2HTML( $tml, $session->{webName}, $session->{topicName} ); | ||||
734 | |||||
735 | # Add the secret id to trigger reconversion. Doesn't work if the | ||||
736 | # editor eats HTML comments, so the editor may need to put it back | ||||
737 | # in during final cleanup. | ||||
738 | $html = '<!--' . $SECRET_ID . '-->' . $html; | ||||
739 | |||||
740 | returnRESTResult( $response, 200, $html ); | ||||
741 | |||||
742 | return; # to prevent further processing | ||||
743 | } | ||||
744 | |||||
745 | # Rest handler for use from Javascript | ||||
746 | sub _restHTML2TML { | ||||
747 | my ( $session, $plugin, $verb, $response ) = @_; | ||||
748 | unless ($html2tml) { | ||||
749 | require Foswiki::Plugins::WysiwygPlugin::HTML2TML; | ||||
750 | |||||
751 | $html2tml = new Foswiki::Plugins::WysiwygPlugin::HTML2TML(); | ||||
752 | } | ||||
753 | my $html = Foswiki::Func::getCgiQuery()->param('text'); | ||||
754 | |||||
755 | #print STDERR "param [". Foswiki::Plugins::WysiwygPlugin::HTML2TML::debugEncode($html). "]\n\n"; | ||||
756 | |||||
757 | $html = RESTParameter2SiteCharSet($html); | ||||
758 | |||||
759 | #print STDERR "paraminSC [". Foswiki::Plugins::WysiwygPlugin::HTML2TML::debugEncode($html). "]\n\n"; | ||||
760 | |||||
761 | $html =~ s/<!--$SECRET_ID-->//go; | ||||
762 | my $tml = $html2tml->convert( | ||||
763 | $html, | ||||
764 | { | ||||
765 | web => $session->{webName}, | ||||
766 | topic => $session->{topicName}, | ||||
767 | convertImage => \&_convertImage, | ||||
768 | rewriteURL => \&postConvertURL, | ||||
769 | very_clean => 1, | ||||
770 | } | ||||
771 | ); | ||||
772 | |||||
773 | #print STDERR "tml inSc [". Foswiki::Plugins::WysiwygPlugin::HTML2TML::debugEncode($tml). "]\n\n"; | ||||
774 | |||||
775 | returnRESTResult( $response, 200, $tml ); | ||||
776 | return; # to prevent further processing | ||||
777 | } | ||||
778 | |||||
779 | # SMELL: foswiki supports proper REST usage of the upload script, | ||||
780 | # so debatable if this is required any more | ||||
781 | sub _restUpload { | ||||
782 | my ( $session, $plugin, $verb, $response ) = @_; | ||||
783 | my $query = Foswiki::Func::getCgiQuery(); | ||||
784 | |||||
785 | # Item1458 ignore uploads not using POST | ||||
786 | if ( $query && $query->method() && uc( $query->method() ) ne 'POST' ) { | ||||
787 | returnRESTResult( $response, 405, "Method not Allowed" ); | ||||
788 | return; | ||||
789 | } | ||||
790 | my ( $web, $topic ) = ( $session->{webName}, $session->{topicName} ); | ||||
791 | my $hideFile = $query->param('hidefile') || ''; | ||||
792 | my $fileComment = $query->param('filecomment') || ''; | ||||
793 | my $createLink = $query->param('createlink') || ''; | ||||
794 | my $doPropsOnly = $query->param('changeproperties'); | ||||
795 | my $filePath = $query->param('filepath') || ''; | ||||
796 | my $fileName = $query->param('filename') || ''; | ||||
797 | if ( $filePath && !$fileName ) { | ||||
798 | $filePath =~ m|([^/\\]*$)|; | ||||
799 | $fileName = $1; | ||||
800 | } | ||||
801 | $fileComment =~ s/\s+/ /go; | ||||
802 | $fileComment =~ s/^\s*//o; | ||||
803 | $fileComment =~ s/\s*$//o; | ||||
804 | $fileName =~ s/\s*$//o; | ||||
805 | $filePath =~ s/\s*$//o; | ||||
806 | |||||
807 | unless ( | ||||
808 | Foswiki::Func::checkAccessPermission( | ||||
809 | 'CHANGE', Foswiki::Func::getWikiName(), | ||||
810 | undef, $topic, $web | ||||
811 | ) | ||||
812 | ) | ||||
813 | { | ||||
814 | returnRESTResult( $response, 401, "Access denied" ); | ||||
815 | return; # to prevent further processing | ||||
816 | } | ||||
817 | |||||
818 | my ( $fileSize, $fileDate, $tmpFileName ); | ||||
819 | |||||
820 | my $stream; | ||||
821 | $stream = $query->upload('filepath') unless $doPropsOnly; | ||||
822 | my $origName = $fileName; | ||||
823 | |||||
824 | unless ($doPropsOnly) { | ||||
825 | |||||
826 | # SMELL: call to unpublished function | ||||
827 | ( $fileName, $origName ) = | ||||
828 | Foswiki::Sandbox::sanitizeAttachmentName($fileName); | ||||
829 | |||||
830 | # check if upload has non zero size | ||||
831 | if ($stream) { | ||||
832 | my @stats = stat $stream; | ||||
833 | $fileSize = $stats[7]; | ||||
834 | $fileDate = $stats[9]; | ||||
835 | } | ||||
836 | |||||
837 | unless ( $fileSize && $fileName ) { | ||||
838 | returnRESTResult( $response, 500, "Zero-sized file upload" ); | ||||
839 | return; # to prevent further processing | ||||
840 | } | ||||
841 | |||||
842 | my $maxSize = Foswiki::Func::getPreferencesValue('ATTACHFILESIZELIMIT') | ||||
843 | || 0; | ||||
844 | $maxSize =~ s/\s+$//; | ||||
845 | $maxSize = 0 unless ( $maxSize =~ /([0-9]+)/o ); | ||||
846 | |||||
847 | if ( $maxSize && $fileSize > $maxSize * 1024 ) { | ||||
848 | returnRESTResult( $response, 500, "OVERSIZED UPLOAD" ); | ||||
849 | return; # to prevent further processing | ||||
850 | } | ||||
851 | } | ||||
852 | |||||
853 | # SMELL: use of undocumented CGI::tmpFileName | ||||
854 | my $tfp = $query->tmpFileName( $query->param('filepath') ); | ||||
855 | my $error; | ||||
856 | try { | ||||
857 | Foswiki::Func::saveAttachment( | ||||
858 | $web, $topic, | ||||
859 | $fileName, | ||||
860 | { | ||||
861 | dontlog => !$Foswiki::cfg{Log}{upload}, | ||||
862 | comment => $fileComment, | ||||
863 | hide => $hideFile, | ||||
864 | createlink => $createLink, | ||||
865 | stream => $stream, | ||||
866 | filepath => $filePath, | ||||
867 | filesize => $fileSize, | ||||
868 | filedate => $fileDate, | ||||
869 | tmpFilename => $tfp, | ||||
870 | } | ||||
871 | ); | ||||
872 | } | ||||
873 | catch Error::Simple with { | ||||
874 | $error = shift->{-text}; | ||||
875 | } | ||||
876 | finally { | ||||
877 | close($stream) if $stream; | ||||
878 | }; | ||||
879 | |||||
880 | if ($error) { | ||||
881 | returnRESTResult( $response, 500, $error ); | ||||
882 | return; # to prevent further processing | ||||
883 | } | ||||
884 | |||||
885 | # Otherwise allow the rest dispatcher to write a 200 | ||||
886 | return "$origName attached to $web.$topic" | ||||
887 | . ( $origName ne $fileName ? " as $fileName" : '' ); | ||||
888 | } | ||||
889 | |||||
890 | sub _unquote { | ||||
891 | my $text = shift; | ||||
892 | $text =~ s/\\/\\\\/g; | ||||
893 | $text =~ s/\n/\\n/g; | ||||
894 | $text =~ s/\r/\\r/g; | ||||
895 | $text =~ s/\t/\\t/g; | ||||
896 | $text =~ s/"/\\"/g; | ||||
897 | $text =~ s/'/\\'/g; | ||||
898 | return $text; | ||||
899 | } | ||||
900 | |||||
901 | # Get, and return, a list of attachments using JSON | ||||
902 | sub _restAttachments { | ||||
903 | my ( $session, $plugin, $verb, $response ) = @_; | ||||
904 | my ( $web, $topic ) = ( $session->{webName}, $session->{topicName} ); | ||||
905 | my ( $meta, $text ) = Foswiki::Func::readTopic( $web, $topic ); | ||||
906 | |||||
907 | unless ( | ||||
908 | Foswiki::Func::checkAccessPermission( | ||||
909 | 'VIEW', Foswiki::Func::getWikiName(), | ||||
910 | $text, $topic, $web, $meta | ||||
911 | ) | ||||
912 | ) | ||||
913 | { | ||||
914 | returnRESTResult( $response, 401, "Access denied" ); | ||||
915 | return; # to prevent further processing | ||||
916 | } | ||||
917 | |||||
918 | # Create a JSON list of attachment data, sorted by name | ||||
919 | my @atts; | ||||
920 | foreach my $att ( sort { $a->{name} cmp $b->{name} } | ||||
921 | $meta->find('FILEATTACHMENT') ) | ||||
922 | { | ||||
923 | push( | ||||
924 | @atts, | ||||
925 | '{' . join( | ||||
926 | ',', | ||||
927 | map { | ||||
928 | '"' | ||||
929 | . _unquote($_) . '":"' | ||||
930 | . _unquote( $att->{$_} ) . '"' | ||||
931 | } keys %$att | ||||
932 | ) | ||||
933 | . '}' | ||||
934 | ); | ||||
935 | |||||
936 | } | ||||
937 | return '[' . join( ',', @atts ) . ']'; | ||||
938 | } | ||||
939 | |||||
940 | 1 | 7µs | 1; | ||
941 | __END__ |