root/branches/2.0/xmlrpc.php

Revision 5768, 36.5 kB (checked in by markjaquith, 1 year ago)

escape before extracting. Props Alexander Concha.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1 <?php
2
3 define('XMLRPC_REQUEST', true);
4
5 // Some browser-embedded clients send cookies. We don't want them.
6 $_COOKIE = array();
7
8 # fix for mozBlog and other cases where '<?xml' isn't on the very first line
9 if ( isset($HTTP_RAW_POST_DATA) )
10     $HTTP_RAW_POST_DATA = trim($HTTP_RAW_POST_DATA);
11
12 include('./wp-config.php');
13
14 if ( isset( $_GET['rsd'] ) ) { // http://archipelago.phrasewise.com/rsd
15 header('Content-type: text/xml; charset=' . get_settings('blog_charset'), true);
16
17 ?>
18 <?php echo '<?xml version="1.0" encoding="'.get_settings('blog_charset').'"?'.'>'; ?>
19 <rsd version="1.0" xmlns="http://archipelago.phrasewise.com/rsd">
20   <service>
21     <engineName>WordPress</engineName>
22     <engineLink>http://wordpress.org/</engineLink>
23     <homePageLink><?php bloginfo_rss('url') ?></homePageLink>
24     <apis>
25       <api name="Movable Type" blogID="1" preferred="true" apiLink="<?php bloginfo_rss('url') ?>/xmlrpc.php" />
26       <api name="MetaWeblog" blogID="1" preferred="false" apiLink="<?php bloginfo_rss('url') ?>/xmlrpc.php" />
27       <api name="Blogger" blogID="1" preferred="false" apiLink="<?php bloginfo_rss('url') ?>/xmlrpc.php" />
28     </apis>
29   </service>
30 </rsd>
31 <?php
32 exit;
33 }
34
35 include_once(ABSPATH . WPINC . '/class-IXR.php');
36
37 // Turn off all warnings and errors.
38 // error_reporting(0);
39
40 $post_default_title = ""; // posts submitted via the xmlrpc interface get that title
41
42 $xmlrpc_logging = 0;
43
44 function logIO($io,$msg) {
45     global $xmlrpc_logging;
46     if ($xmlrpc_logging) {
47         $fp = fopen("../xmlrpc.log","a+");
48         $date = gmdate("Y-m-d H:i:s ");
49         $iot = ($io == "I") ? " Input: " : " Output: ";
50         fwrite($fp, "\n\n".$date.$iot.$msg);
51         fclose($fp);
52     }
53     return true;
54     }
55
56 function starify($string) {
57     $i = strlen($string);
58     return str_repeat('*', $i);
59 }
60
61 if ( isset($HTTP_RAW_POST_DATA) )
62   logIO("I", $HTTP_RAW_POST_DATA);
63
64
65 class wp_xmlrpc_server extends IXR_Server {
66
67     function wp_xmlrpc_server() {
68         $this->methods = array(
69           // Blogger API
70           'blogger.getUsersBlogs' => 'this:blogger_getUsersBlogs',
71           'blogger.getUserInfo' => 'this:blogger_getUserInfo',
72           'blogger.getPost' => 'this:blogger_getPost',
73           'blogger.getRecentPosts' => 'this:blogger_getRecentPosts',
74           'blogger.getTemplate' => 'this:blogger_getTemplate',
75           'blogger.setTemplate' => 'this:blogger_setTemplate',
76           'blogger.newPost' => 'this:blogger_newPost',
77           'blogger.editPost' => 'this:blogger_editPost',
78           'blogger.deletePost' => 'this:blogger_deletePost',
79
80           // MetaWeblog API (with MT extensions to structs)
81           'metaWeblog.newPost' => 'this:mw_newPost',
82           'metaWeblog.editPost' => 'this:mw_editPost',
83           'metaWeblog.getPost' => 'this:mw_getPost',
84           'metaWeblog.getRecentPosts' => 'this:mw_getRecentPosts',
85           'metaWeblog.getCategories' => 'this:mw_getCategories',
86           'metaWeblog.newMediaObject' => 'this:mw_newMediaObject',
87
88           // MetaWeblog API aliases for Blogger API
89           // see http://www.xmlrpc.com/stories/storyReader$2460
90           'metaWeblog.deletePost' => 'this:blogger_deletePost',
91           'metaWeblog.getTemplate' => 'this:blogger_getTemplate',
92           'metaWeblog.setTemplate' => 'this:blogger_setTemplate',
93           'metaWeblog.getUsersBlogs' => 'this:blogger_getUsersBlogs',
94
95           // MovableType API
96           'mt.getCategoryList' => 'this:mt_getCategoryList',
97           'mt.getRecentPostTitles' => 'this:mt_getRecentPostTitles',
98           'mt.getPostCategories' => 'this:mt_getPostCategories',
99           'mt.setPostCategories' => 'this:mt_setPostCategories',
100           'mt.supportedMethods' => 'this:mt_supportedMethods',
101           'mt.supportedTextFilters' => 'this:mt_supportedTextFilters',
102           'mt.getTrackbackPings' => 'this:mt_getTrackbackPings',
103           'mt.publishPost' => 'this:mt_publishPost',
104
105           // PingBack
106           'pingback.ping' => 'this:pingback_ping',
107           'pingback.extensions.getPingbacks' => 'this:pingback_extensions_getPingbacks',
108
109           'demo.sayHello' => 'this:sayHello',
110           'demo.addTwoNumbers' => 'this:addTwoNumbers'
111         );
112         $this->methods = apply_filters('xmlrpc_methods', $this->methods);
113         $this->IXR_Server($this->methods);
114     }
115
116     function sayHello($args) {
117         return 'Hello!';
118     }
119
120     function addTwoNumbers($args) {
121         $number1 = $args[0];
122         $number2 = $args[1];
123         return $number1 + $number2;
124     }
125
126     function login_pass_ok($user_login, $user_pass) {
127       if (!user_pass_ok($user_login, $user_pass)) {
128         $this->error = new IXR_Error(403, 'Bad login/pass combination.');
129         return false;
130       }
131       return true;
132     }
133
134     function escape(&$array) {
135         global $wpdb;
136
137         foreach ( (array) $array as $k => $v ) {
138             if (is_array($v)) {
139                 $this->escape($array[$k]);
140             } else if (is_object($v)) {
141                 //skip
142             } else {
143                 $array[$k] = $wpdb->escape($v);
144             }
145         }
146     }
147
148     /* Blogger API functions
149      * specs on http://plant.blogger.com/api and http://groups.yahoo.com/group/bloggerDev/
150      */
151
152
153     /* blogger.getUsersBlogs will make more sense once we support multiple blogs */
154     function blogger_getUsersBlogs($args) {
155
156         $this->escape($args);
157
158       $user_login = $args[1];
159       $user_pass  = $args[2];
160
161       if (!$this->login_pass_ok($user_login, $user_pass)) {
162         return $this->error;
163       }
164
165       set_current_user(0, $user_login);
166       $is_admin = current_user_can('level_8');
167
168       $struct = array(
169         'isAdmin'  => $is_admin,
170         'url'      => get_settings('home') . '/',
171         'blogid'   => '1',
172         'blogName' => get_settings('blogname')
173       );
174
175       return array($struct);
176     }
177
178
179     /* blogger.getUsersInfo gives your client some info about you, so you don't have to */
180     function blogger_getUserInfo($args) {
181
182         $this->escape($args);
183
184       $user_login = $args[1];
185       $user_pass  = $args[2];
186
187       if (!$this->login_pass_ok($user_login, $user_pass)) {
188         return $this->error;
189       }
190
191       $user_data = get_userdatabylogin($user_login);
192
193       $struct = array(
194         'nickname'  => $user_data->nickname,
195         'userid'    => $user_data->ID,
196         'url'       => $user_data->user_url,
197         'email'     => $user_data->user_email,
198         'lastname'  => $user_data->last_name,
199         'firstname' => $user_data->first_name
200       );
201
202       return $struct;
203     }
204
205
206     /* blogger.getPost ...gets a post */
207     function blogger_getPost($args) {
208
209         $this->escape($args);
210
211         $post_ID    = (int) $args[1];
212         $user_login = $args[2];
213         $user_pass  = $args[3];
214
215       if (!$this->login_pass_ok($user_login, $user_pass)) {
216         return $this->error;
217       }
218
219       $user_data = get_userdatabylogin($user_login);
220       $post_data = wp_get_single_post($post_ID, ARRAY_A);
221
222       $categories = implode(',', wp_get_post_cats(1, $post_ID));
223
224       $content  = '<title>'.stripslashes($post_data['post_title']).'</title>';
225       $content .= '<category>'.$categories.'</category>';
226       $content .= stripslashes($post_data['post_content']);
227
228       $struct = array(
229         'userid'    => $post_data['post_author'],
230         'dateCreated' => new IXR_Date(mysql2date('Ymd\TH:i:s', $post_data['post_date'])),
231         'content'     => $content,
232         'postid'  => $post_data['ID']
233       );
234
235       return $struct;
236     }
237
238
239     /* blogger.getRecentPosts ...gets recent posts */
240     function blogger_getRecentPosts($args) {
241
242       global $wpdb;
243
244         $this->escape($args);
245
246         $blog_ID    = (int) $args[1]; /* though we don't use it yet */
247         $user_login = $args[2];
248         $user_pass  = $args[3];
249         $num_posts  = $args[4];
250
251       if (!$this->login_pass_ok($user_login, $user_pass)) {
252         return $this->error;
253       }
254
255       $posts_list = wp_get_recent_posts($num_posts);
256
257       if (!$posts_list) {
258         $this->error = new IXR_Error(500, 'Either there are no posts, or something went wrong.');
259         return $this->error;
260       }
261
262       foreach ($posts_list as $entry) {
263       
264         $post_date = mysql2date('Ymd\TH:i:s', $entry['post_date']);
265         $categories = implode(',', wp_get_post_cats(1, $entry['ID']));
266
267         $content  = '<title>'.stripslashes($entry['post_title']).'</title>';
268         $content .= '<category>'.$categories.'</category>';
269         $content .= stripslashes($entry['post_content']);
270
271         $struct[] = array(
272           'userid' => $entry['post_author'],
273           'dateCreated' => new IXR_Date($post_date),
274           'content' => $content,
275           'postid' => $entry['ID'],
276         );
277
278       }
279
280       $recent_posts = array();
281       for ($j=0; $j<count($struct); $j++) {
282         array_push($recent_posts, $struct[$j]);
283       }
284
285       return $recent_posts;
286     }
287
288
289     /* blogger.getTemplate returns your blog_filename */
290     function blogger_getTemplate($args) {
291
292         $this->escape($args);
293
294       $blog_ID    = (int) $args[1];
295       $user_login = $args[2];
296       $user_pass  = $args[3];
297       $template   = $args[4]; /* could be 'main' or 'archiveIndex', but we don't use it */
298
299       if (!$this->login_pass_ok($user_login, $user_pass)) {
300         return $this->error;
301       }
302
303       set_current_user(0, $user_login);
304       if ( !current_user_can('edit_themes') ) {
305         return new IXR_Error(401, 'Sorry, this user can not edit the template.');
306       }
307
308       /* warning: here we make the assumption that the weblog's URI is on the same server */
309       $filename = get_settings('home') . '/';
310       $filename = preg_replace('#https?://.+?/#', $_SERVER['DOCUMENT_ROOT'].'/', $filename);
311
312       $f = fopen($filename, 'r');
313       $content = fread($f, filesize($filename));
314       fclose($f);
315
316       /* so it is actually editable with a windows/mac client */
317       // FIXME: (or delete me) do we really want to cater to bad clients at the expense of good ones by BEEPing up their line breaks? commented.     $content = str_replace("\n", "\r\n", $content);
318
319       return $content;
320     }
321
322
323     /* blogger.setTemplate updates the content of blog_filename */
324     function blogger_setTemplate($args) {
325
326         $this->escape($args);
327
328       $blog_ID    = (int) $args[1];
329       $user_login = $args[2];
330       $user_pass  = $args[3];
331       $content    = $args[4];
332       $template   = $args[5]; /* could be 'main' or 'archiveIndex', but we don't use it */
333
334       if (!$this->login_pass_ok($user_login, $user_pass)) {
335         return $this->error;
336       }
337
338       set_current_user(0, $user_login);
339       if ( !current_user_can('edit_themes') ) {
340         return new IXR_Error(401, 'Sorry, this user can not edit the template.');
341       }
342
343       /* warning: here we make the assumption that the weblog's URI is on the same server */
344       $filename = get_settings('home') . '/';
345       $filename = preg_replace('#https?://.+?/#', $_SERVER['DOCUMENT_ROOT'].'/', $filename);
346
347       if ($f = fopen($filename, 'w+')) {
348         fwrite($f, $content);
349         fclose($f);
350       } else {
351         return new IXR_Error(500, 'Either the file is not writable, or something wrong happened. The file has not been updated.');
352       }
353
354       return true;
355     }
356
357
358     /* blogger.newPost ...creates a new post */
359     function blogger_newPost($args) {
360
361       global $wpdb;
362
363         $this->escape($args);
364
365       $blog_ID    = (int) $args[1]; /* though we don't use it yet */
366       $user_login = $args[2];
367       $user_pass  = $args[3];
368       $content    = $args[4];
369       $publish    = $args[5];
370
371       if (!$this->login_pass_ok($user_login, $user_pass)) {
372         return $this->error;
373       }
374       
375       $cap = ($publish) ? 'publish_posts' : 'edit_posts';
376       $user = set_current_user(0, $user_login);
377       if ( !current_user_can($cap) )
378         return new IXR_Error(401, 'Sorry, you can not post on this weblog or category.');
379
380       $post_status = ($publish) ? 'publish' : 'draft';
381
382       $post_author = $user->ID;
383
384       $post_title = xmlrpc_getposttitle($content);
385       $post_category = xmlrpc_getpostcategory($content);
386       $post_content = xmlrpc_removepostdata($content);
387
388       $post_date = current_time('mysql');
389       $post_date_gmt = current_time('mysql', 1);
390
391       $post_data = compact('blog_ID', 'post_author', 'post_date', 'post_date_gmt', 'post_content', 'post_title', 'post_category', 'post_status');
392
393       $post_ID = wp_insert_post($post_data);
394
395       if (!$post_ID) {
396         return new IXR_Error(500, 'Sorry, your entry could not be posted. Something wrong happened.');
397       }
398
399       logIO('O', "Posted ! ID: $post_ID");
400
401       return $post_ID;
402     }
403
404
405     /* blogger.editPost ...edits a post */
406     function blogger_editPost($args) {
407
408       global $wpdb;
409
410         $this->escape($args);
411
412       $post_ID     = (int) $args[1];
413       $user_login  = $args[2];
414       $user_pass   = $args[3];
415       $content     = $args[4];
416       $publish     = $args[5];
417
418       if (!$this->login_pass_ok($user_login, $user_pass)) {
419         return $this->error;
420       }
421
422       $actual_post = wp_get_single_post($post_ID,ARRAY_A);
423
424       if (!$actual_post) {
425           return new IXR_Error(404, 'Sorry, no such post.');
426       }
427
428         $this->escape($actual_post);
429
430       set_current_user(0, $user_login);
431       if ( !current_user_can('edit_post', $post_ID) )
432         return new IXR_Error(401, 'Sorry, you do not have the right to edit this post.');
433
434       extract($actual_post, EXTR_SKIP);
435
436       if ( ('publish' == $post_status) && !current_user_can('publish_posts') )
437           return new IXR_Error(401, 'Sorry, you do not have the right to publish this post.');
438
439       $post_title = xmlrpc_getposttitle($content);
440       $post_category = xmlrpc_getpostcategory($content);
441       $post_content = xmlrpc_removepostdata($content);
442
443       $postdata = compact('ID', 'post_content', 'post_title', 'post_category', 'post_status', 'post_excerpt');
444
445       $result = wp_update_post($postdata);
446
447       if (!$result) {
448           return new IXR_Error(500, 'For some strange yet very annoying reason, this post could not be edited.');
449       }
450
451       return true;
452     }
453
454
455     /* blogger.deletePost ...deletes a post */
456     function blogger_deletePost($args) {
457
458       global $wpdb;
459
460         $this->escape($args);
461
462       $post_ID     = (int) $args[1];
463       $user_login  = $args[2];
464       $user_pass   = $args[3];
465       $publish     = $args[4];
466
467       if (!$this->login_pass_ok($user_login, $user_pass)) {
468         return $this->error;
469       }
470
471       $actual_post = wp_get_single_post($post_ID,ARRAY_A);
472
473       if (!$actual_post) {
474           return new IXR_Error(404, 'Sorry, no such post.');
475       }
476
477       set_current_user(0, $user_login);
478       if ( !current_user_can('edit_post', $post_ID) )
479         return new IXR_Error(401, 'Sorry, you do not have the right to delete this post.');
480
481       $result = wp_delete_post($post_ID);
482
483       if (!$result) {
484           return new IXR_Error(500, 'For some strange yet very annoying reason, this post could not be deleted.');
485       }
486
487       return true;
488     }
489
490
491
492     /* MetaWeblog API functions
493      * specs on wherever Dave Winer wants them to be
494      */
495
496     /* metaweblog.newPost creates a post */
497     function mw_newPost($args) {
498
499       global $wpdb, $post_default_category;
500
501         $this->escape($args);
502
503       $blog_ID     = (int) $args[0]; // we will support this in the near future
504       $user_login  = $args[1];
505       $user_pass   = $args[2];
506       $content_struct = $args[3];
507       $publish     = $args[4];
508
509       if (!$this->login_pass_ok($user_login, $user_pass)) {
510         return $this->error;
511       }
512
513       $user = set_current_user(0, $user_login);
514       if ( !current_user_can('publish_posts') )
515         return new IXR_Error(401, 'Sorry, you can not post on this weblog or category.');
516
517       $post_author = $user->ID;
518
519       $post_title = $content_struct['title'];
520       $post_content = apply_filters( 'content_save_pre', $content_struct['description'] );
521       $post_status = $publish ? 'publish' : 'draft';
522
523       $post_excerpt = $content_struct['mt_excerpt'];
524       $post_more = $content_struct['mt_text_more'];
525
526