1 /*
   2  * Copyright (c) 2003, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 #include <jni.h>
  27 #include <jni_util.h>
  28 #include <jvm_md.h>
  29 #include <dlfcn.h>
  30 #include <cups/cups.h>
  31 #include <cups/ppd.h>
  32 
  33 //#define CUPS_DEBUG
  34 
  35 #ifdef CUPS_DEBUG
  36 #define DPRINTF(x, y) fprintf(stderr, x, y);
  37 #else
  38 #define DPRINTF(x, y)
  39 #endif
  40 
  41 typedef const char* (*fn_cupsServer)(void);
  42 typedef int (*fn_ippPort)(void);
  43 typedef http_t* (*fn_httpConnect)(const char *, int);
  44 typedef void (*fn_httpClose)(http_t *);
  45 typedef char* (*fn_cupsGetPPD)(const char *);
  46 typedef cups_dest_t* (*fn_cupsGetDest)(const char *name,
  47     const char *instance, int num_dests, cups_dest_t *dests);
  48 typedef int (*fn_cupsGetDests)(cups_dest_t **dests);
  49 typedef void (*fn_cupsFreeDests)(int num_dests, cups_dest_t *dests);
  50 typedef ppd_file_t* (*fn_ppdOpenFile)(const char *);
  51 typedef void (*fn_ppdClose)(ppd_file_t *);
  52 typedef ppd_option_t* (*fn_ppdFindOption)(ppd_file_t *, const char *);
  53 typedef ppd_size_t* (*fn_ppdPageSize)(ppd_file_t *, char *);
  54 
  55 fn_cupsServer j2d_cupsServer;
  56 fn_ippPort j2d_ippPort;
  57 fn_httpConnect j2d_httpConnect;
  58 fn_httpClose j2d_httpClose;
  59 fn_cupsGetPPD j2d_cupsGetPPD;
  60 fn_cupsGetDest j2d_cupsGetDest;
  61 fn_cupsGetDests j2d_cupsGetDests;
  62 fn_cupsFreeDests j2d_cupsFreeDests;
  63 fn_ppdOpenFile j2d_ppdOpenFile;
  64 fn_ppdClose j2d_ppdClose;
  65 fn_ppdFindOption j2d_ppdFindOption;
  66 fn_ppdPageSize j2d_ppdPageSize;
  67 
  68 
  69 /*
  70  * Initialize library functions.
  71  * // REMIND : move tab , add dlClose before return
  72  */
  73 JNIEXPORT jboolean JNICALL
  74 Java_sun_print_CUPSPrinter_initIDs(JNIEnv *env,
  75                                          jobject printObj) {
  76   void *handle = dlopen(VERSIONED_JNI_LIB_NAME("cups", "2"),
  77                         RTLD_LAZY | RTLD_GLOBAL);
  78 
  79   if (handle == NULL) {
  80     handle = dlopen(JNI_LIB_NAME("cups"), RTLD_LAZY | RTLD_GLOBAL);
  81     if (handle == NULL) {
  82       return JNI_FALSE;
  83     }
  84   }
  85 
  86   j2d_cupsServer = (fn_cupsServer)dlsym(handle, "cupsServer");
  87   if (j2d_cupsServer == NULL) {
  88     dlclose(handle);
  89     return JNI_FALSE;
  90   }
  91 
  92   j2d_ippPort = (fn_ippPort)dlsym(handle, "ippPort");
  93   if (j2d_ippPort == NULL) {
  94     dlclose(handle);
  95     return JNI_FALSE;
  96   }
  97 
  98   j2d_httpConnect = (fn_httpConnect)dlsym(handle, "httpConnect");
  99   if (j2d_httpConnect == NULL) {
 100     dlclose(handle);
 101     return JNI_FALSE;
 102   }
 103 
 104   j2d_httpClose = (fn_httpClose)dlsym(handle, "httpClose");
 105   if (j2d_httpClose == NULL) {
 106     dlclose(handle);
 107     return JNI_FALSE;
 108   }
 109 
 110   j2d_cupsGetPPD = (fn_cupsGetPPD)dlsym(handle, "cupsGetPPD");
 111   if (j2d_cupsGetPPD == NULL) {
 112     dlclose(handle);
 113     return JNI_FALSE;
 114   }
 115 
 116   j2d_cupsGetDest = (fn_cupsGetDest)dlsym(handle, "cupsGetDest");
 117   if (j2d_cupsGetDest == NULL) {
 118     dlclose(handle);
 119     return JNI_FALSE;
 120   }
 121 
 122   j2d_cupsGetDests = (fn_cupsGetDests)dlsym(handle, "cupsGetDests");
 123   if (j2d_cupsGetDests == NULL) {
 124     dlclose(handle);
 125     return JNI_FALSE;
 126   }
 127 
 128   j2d_cupsFreeDests = (fn_cupsFreeDests)dlsym(handle, "cupsFreeDests");
 129   if (j2d_cupsFreeDests == NULL) {
 130     dlclose(handle);
 131     return JNI_FALSE;
 132   }
 133 
 134   j2d_ppdOpenFile = (fn_ppdOpenFile)dlsym(handle, "ppdOpenFile");
 135   if (j2d_ppdOpenFile == NULL) {
 136     dlclose(handle);
 137     return JNI_FALSE;
 138 
 139   }
 140 
 141   j2d_ppdClose = (fn_ppdClose)dlsym(handle, "ppdClose");
 142   if (j2d_ppdClose == NULL) {
 143     dlclose(handle);
 144     return JNI_FALSE;
 145 
 146   }
 147 
 148   j2d_ppdFindOption = (fn_ppdFindOption)dlsym(handle, "ppdFindOption");
 149   if (j2d_ppdFindOption == NULL) {
 150     dlclose(handle);
 151     return JNI_FALSE;
 152   }
 153 
 154   j2d_ppdPageSize = (fn_ppdPageSize)dlsym(handle, "ppdPageSize");
 155   if (j2d_ppdPageSize == NULL) {
 156     dlclose(handle);
 157     return JNI_FALSE;
 158   }
 159 
 160   return JNI_TRUE;
 161 }
 162 
 163 /*
 164  * Gets CUPS server name.
 165  *
 166  */
 167 JNIEXPORT jstring JNICALL
 168 Java_sun_print_CUPSPrinter_getCupsServer(JNIEnv *env,
 169                                          jobject printObj)
 170 {
 171     jstring cServer = NULL;
 172     const char* server = j2d_cupsServer();
 173     if (server != NULL) {
 174         // Is this a local domain socket?
 175         if (strncmp(server, "/", 1) == 0) {
 176             cServer = JNU_NewStringPlatform(env, "localhost");
 177         } else {
 178             cServer = JNU_NewStringPlatform(env, server);
 179         }
 180     }
 181     return cServer;
 182 }
 183 
 184 /*
 185  * Gets CUPS port name.
 186  *
 187  */
 188 JNIEXPORT jint JNICALL
 189 Java_sun_print_CUPSPrinter_getCupsPort(JNIEnv *env,
 190                                          jobject printObj)
 191 {
 192     int port = j2d_ippPort();
 193     return (jint) port;
 194 }
 195 
 196 
 197 /*
 198  * Gets CUPS default printer name.
 199  *
 200  */
 201 JNIEXPORT jstring JNICALL
 202 Java_sun_print_CUPSPrinter_getCupsDefaultPrinter(JNIEnv *env,
 203                                                   jobject printObj)
 204 {
 205     jstring cDefPrinter = NULL;
 206     cups_dest_t *dests;
 207     char *defaultPrinter = NULL;
 208     int num_dests = j2d_cupsGetDests(&dests);
 209     int i = 0;
 210     cups_dest_t *dest = j2d_cupsGetDest(NULL, NULL, num_dests, dests);
 211     if (dest != NULL) {
 212         defaultPrinter = dest->name;
 213         if (defaultPrinter != NULL) {
 214             cDefPrinter = JNU_NewStringPlatform(env, defaultPrinter);
 215         }
 216     }
 217     j2d_cupsFreeDests(num_dests, dests);
 218     return cDefPrinter;
 219 }
 220 
 221 /*
 222  * Checks if connection can be made to the server.
 223  *
 224  */
 225 JNIEXPORT jboolean JNICALL
 226 Java_sun_print_CUPSPrinter_canConnect(JNIEnv *env,
 227                                       jobject printObj,
 228                                       jstring server,
 229                                       jint port)
 230 {
 231     const char *serverName;
 232     serverName = (*env)->GetStringUTFChars(env, server, NULL);
 233     if (serverName != NULL) {
 234         http_t *http = j2d_httpConnect(serverName, (int)port);
 235         (*env)->ReleaseStringUTFChars(env, server, serverName);
 236         if (http != NULL) {
 237             j2d_httpClose(http);
 238             return JNI_TRUE;
 239         }
 240     }
 241     return JNI_FALSE;
 242 }
 243 
 244 
 245 /*
 246  * Returns list of media: pages + trays
 247  */
 248 JNIEXPORT jobjectArray JNICALL
 249 Java_sun_print_CUPSPrinter_getMedia(JNIEnv *env,
 250                                          jobject printObj,
 251                                          jstring printer)
 252 {
 253     ppd_file_t *ppd;
 254     ppd_option_t *optionTray, *optionPage;
 255     ppd_choice_t *choice;
 256     const char *name;
 257     const char *filename;
 258     int i, nTrays=0, nPages=0, nTotal=0;
 259     jstring utf_str;
 260     jclass cls;
 261     jobjectArray nameArray = NULL;
 262 
 263     name = (*env)->GetStringUTFChars(env, printer, NULL);
 264     if (name == NULL) {
 265         (*env)->ExceptionClear(env);
 266         JNU_ThrowOutOfMemoryError(env, "Could not create printer name");
 267         return NULL;
 268     }
 269 
 270     // NOTE: cupsGetPPD returns a pointer to a filename of a temporary file.
 271     // unlink() must be caled to remove the file when finished using it.
 272     filename = j2d_cupsGetPPD(name);
 273     (*env)->ReleaseStringUTFChars(env, printer, name);
 274     CHECK_NULL_RETURN(filename, NULL);
 275 
 276     cls = (*env)->FindClass(env, "java/lang/String");
 277     CHECK_NULL_RETURN(cls, NULL);
 278 
 279     if ((ppd = j2d_ppdOpenFile(filename)) == NULL) {
 280         unlink(filename);
 281         DPRINTF("CUPSfuncs::unable to open PPD  %s\n", filename);
 282         return NULL;
 283     }
 284 
 285     optionPage = j2d_ppdFindOption(ppd, "PageSize");
 286     if (optionPage != NULL) {
 287         nPages = optionPage->num_choices;
 288     }
 289 
 290     optionTray = j2d_ppdFindOption(ppd, "InputSlot");
 291     if (optionTray != NULL) {
 292         nTrays = optionTray->num_choices;
 293     }
 294 
 295     if ((nTotal = (nPages+nTrays) *2) > 0) {
 296         nameArray = (*env)->NewObjectArray(env, nTotal, cls, NULL);
 297         if (nameArray == NULL) {
 298             unlink(filename);
 299             j2d_ppdClose(ppd);
 300             DPRINTF("CUPSfuncs::bad alloc new array\n", "")
 301             (*env)->ExceptionClear(env);
 302             JNU_ThrowOutOfMemoryError(env, "OutOfMemoryError");
 303             return NULL;
 304         }
 305 
 306         for (i = 0; optionPage!=NULL && i<nPages; i++) {
 307             choice = (optionPage->choices)+i;
 308             utf_str = JNU_NewStringPlatform(env, choice->text);
 309             if (utf_str == NULL) {
 310                 unlink(filename);
 311                 j2d_ppdClose(ppd);
 312                 DPRINTF("CUPSfuncs::bad alloc new string ->text\n", "")
 313                 JNU_ThrowOutOfMemoryError(env, "OutOfMemoryError");
 314                 return NULL;
 315             }
 316             (*env)->SetObjectArrayElement(env, nameArray, i*2, utf_str);
 317             (*env)->DeleteLocalRef(env, utf_str);
 318             utf_str = JNU_NewStringPlatform(env, choice->choice);
 319             if (utf_str == NULL) {
 320                 unlink(filename);
 321                 j2d_ppdClose(ppd);
 322                 DPRINTF("CUPSfuncs::bad alloc new string ->choice\n", "")
 323                 JNU_ThrowOutOfMemoryError(env, "OutOfMemoryError");
 324                 return NULL;
 325             }
 326             (*env)->SetObjectArrayElement(env, nameArray, i*2+1, utf_str);
 327             (*env)->DeleteLocalRef(env, utf_str);
 328         }
 329 
 330         for (i = 0; optionTray!=NULL && i<nTrays; i++) {
 331             choice = (optionTray->choices)+i;
 332             utf_str = JNU_NewStringPlatform(env, choice->text);
 333             if (utf_str == NULL) {
 334                 unlink(filename);
 335                 j2d_ppdClose(ppd);
 336                 DPRINTF("CUPSfuncs::bad alloc new string text\n", "")
 337                 JNU_ThrowOutOfMemoryError(env, "OutOfMemoryError");
 338                 return NULL;
 339             }
 340             (*env)->SetObjectArrayElement(env, nameArray,
 341                                           (nPages+i)*2, utf_str);
 342             (*env)->DeleteLocalRef(env, utf_str);
 343             utf_str = JNU_NewStringPlatform(env, choice->choice);
 344             if (utf_str == NULL) {
 345                 unlink(filename);
 346                 j2d_ppdClose(ppd);
 347                 DPRINTF("CUPSfuncs::bad alloc new string choice\n", "")
 348                 JNU_ThrowOutOfMemoryError(env, "OutOfMemoryError");
 349                 return NULL;
 350             }
 351             (*env)->SetObjectArrayElement(env, nameArray,
 352                                           (nPages+i)*2+1, utf_str);
 353             (*env)->DeleteLocalRef(env, utf_str);
 354         }
 355     }
 356     j2d_ppdClose(ppd);
 357     unlink(filename);
 358     return nameArray;
 359 }
 360 
 361 
 362 /*
 363  * Returns list of page sizes and imageable area.
 364  */
 365 JNIEXPORT jfloatArray JNICALL
 366 Java_sun_print_CUPSPrinter_getPageSizes(JNIEnv *env,
 367                                          jobject printObj,
 368                                          jstring printer)
 369 {
 370     ppd_file_t *ppd;
 371     ppd_option_t *option;
 372     ppd_choice_t *choice;
 373     ppd_size_t *size;
 374 
 375     const char *name = (*env)->GetStringUTFChars(env, printer, NULL);
 376     if (name == NULL) {
 377         (*env)->ExceptionClear(env);
 378         JNU_ThrowOutOfMemoryError(env, "Could not create printer name");
 379         return NULL;
 380     }
 381     const char *filename;
 382     int i;
 383     jobjectArray sizeArray = NULL;
 384     jfloat *dims;
 385 
 386     // NOTE: cupsGetPPD returns a pointer to a filename of a temporary file.
 387     // unlink() must be called to remove the file after using it.
 388     filename = j2d_cupsGetPPD(name);
 389     (*env)->ReleaseStringUTFChars(env, printer, name);
 390     CHECK_NULL_RETURN(filename, NULL);
 391     if ((ppd = j2d_ppdOpenFile(filename)) == NULL) {
 392         unlink(filename);
 393         DPRINTF("unable to open PPD  %s\n", filename)
 394         return NULL;
 395     }
 396     option = j2d_ppdFindOption(ppd, "PageSize");
 397     if (option != NULL && option->num_choices > 0) {
 398         // create array of dimensions - (num_choices * 6)
 399         //to cover length & height
 400         DPRINTF( "CUPSfuncs::option->num_choices %d\n", option->num_choices)
 401         // +1 is for storing the default media index
 402         sizeArray = (*env)->NewFloatArray(env, option->num_choices*6+1);
 403         if (sizeArray == NULL) {
 404             unlink(filename);
 405             j2d_ppdClose(ppd);
 406             DPRINTF("CUPSfuncs::bad alloc new float array\n", "")
 407             (*env)->ExceptionClear(env);
 408             JNU_ThrowOutOfMemoryError(env, "OutOfMemoryError");
 409             return NULL;
 410         }
 411 
 412         dims = (*env)->GetFloatArrayElements(env, sizeArray, NULL);
 413         if (dims == NULL) {
 414             unlink(filename);
 415             j2d_ppdClose(ppd);
 416             (*env)->ExceptionClear(env);
 417             JNU_ThrowOutOfMemoryError(env, "Could not create printer name");
 418             return NULL;
 419         }
 420         for (i = 0; i<option->num_choices; i++) {
 421             choice = (option->choices)+i;
 422             // get the index of the default page
 423             if (!strcmp(choice->choice, option->defchoice)) {
 424                 dims[option->num_choices*6] = (float)i;
 425             }
 426             size = j2d_ppdPageSize(ppd, choice->choice);
 427             if (size != NULL) {
 428                 // paper width and height
 429                 dims[i*6] = size->width;
 430                 dims[(i*6)+1] = size->length;
 431                 // paper printable area
 432                 dims[(i*6)+2] = size->left;
 433                 dims[(i*6)+3] = size->top;
 434                 dims[(i*6)+4] = size->right;
 435                 dims[(i*6)+5] = size->bottom;
 436             }
 437         }
 438 
 439         (*env)->ReleaseFloatArrayElements(env, sizeArray, dims, 0);
 440     }
 441 
 442     j2d_ppdClose(ppd);
 443     unlink(filename);
 444     return sizeArray;
 445 }
 446 
 447 /*
 448  * Populates the supplied ArrayList<Integer> with resolutions.
 449  * The first pair of elements will be the default resolution.
 450  * If resolution isn't supported the list will be empty.
 451  * If needed we can add a 2nd ArrayList<String> which would
 452  * be populated with the corresponding UI name.
 453  * PPD specifies the syntax for resolution as either "Ndpi" or "MxNdpi",
 454  * eg 300dpi or 600x600dpi. The former is a shorthand where xres==yres.
 455  * We will always expand to the latter as we use a single array list.
 456  * Note: getMedia() and getPageSizes() both open the ppd file
 457  * This is not going to scale forever so if we add anymore we
 458  * should look to consolidate this.
 459  */
 460 JNIEXPORT void JNICALL
 461 Java_sun_print_CUPSPrinter_getResolutions(JNIEnv *env,
 462                                           jobject printObj,
 463                                           jstring printer,
 464                                           jobject arrayList)
 465 {
 466     ppd_file_t *ppd = NULL;
 467     ppd_option_t *resolution;
 468     int defx = 0, defy = 0;
 469     int resx = 0, resy = 0;
 470     jclass intCls, cls;
 471     jmethodID intCtr, arrListAddMID;
 472     int i;
 473 
 474     intCls = (*env)->FindClass(env, "java/lang/Integer");
 475     CHECK_NULL(intCls);
 476     intCtr = (*env)->GetMethodID(env, intCls, "<init>", "(I)V");
 477     CHECK_NULL(intCtr);
 478     cls = (*env)->FindClass(env, "java/util/ArrayList");
 479     CHECK_NULL(cls);
 480     arrListAddMID =
 481         (*env)->GetMethodID(env, cls, "add", "(Ljava/lang/Object;)Z");
 482     CHECK_NULL(arrListAddMID);
 483 
 484     const char *name = (*env)->GetStringUTFChars(env, printer, NULL);
 485     if (name == NULL) {
 486         (*env)->ExceptionClear(env);
 487         JNU_ThrowOutOfMemoryError(env, "Could not create printer name");
 488         return;
 489     }
 490     const char *filename;
 491 
 492     // NOTE: cupsGetPPD returns a pointer to a filename of a temporary file.
 493     // unlink() must be called to remove the file after using it.
 494     filename = j2d_cupsGetPPD(name);
 495     (*env)->ReleaseStringUTFChars(env, printer, name);
 496     CHECK_NULL(filename);
 497     if ((ppd = j2d_ppdOpenFile(filename)) == NULL) {
 498         unlink(filename);
 499         DPRINTF("unable to open PPD  %s\n", filename)
 500     }
 501     resolution = j2d_ppdFindOption(ppd, "Resolution");
 502     if (resolution != NULL) {
 503         int matches = sscanf(resolution->defchoice, "%dx%ddpi", &defx, &defy);
 504         if (matches == 2) {
 505            if (defx <= 0 || defy <= 0) {
 506               defx = 0;
 507               defy = 0;
 508            }
 509         } else {
 510             matches = sscanf(resolution->defchoice, "%ddpi", &defx);
 511             if (matches == 1) {
 512                 if (defx <= 0) {
 513                    defx = 0;
 514                 } else {
 515                    defy = defx;
 516                 }
 517             }
 518         }
 519         if (defx > 0) {
 520           jobject rxObj, ryObj;
 521           rxObj = (*env)->NewObject(env, intCls, intCtr, defx);
 522           CHECK_NULL(rxObj);
 523           ryObj = (*env)->NewObject(env, intCls, intCtr, defy);
 524           CHECK_NULL(ryObj);
 525           (*env)->CallBooleanMethod(env, arrayList, arrListAddMID, rxObj);
 526           (*env)->CallBooleanMethod(env, arrayList, arrListAddMID, ryObj);
 527         }
 528 
 529         for (i = 0; i < resolution->num_choices; i++) {
 530             char *resStr = resolution->choices[i].choice;
 531             int matches = sscanf(resStr, "%dx%ddpi", &resx, &resy);
 532             if (matches == 2) {
 533                if (resx <= 0 || resy <= 0) {
 534                   resx = 0;
 535                   resy = 0;
 536                }
 537             } else {
 538                 matches = sscanf(resStr, "%ddpi", &resx);
 539                 if (matches == 1) {
 540                     if (resx <= 0) {
 541                        resx = 0;
 542                     } else {
 543                        resy = resx;
 544                     }
 545                 }
 546             }
 547             if (resx > 0 && (resx != defx || resy != defy )) {
 548               jobject rxObj, ryObj;
 549               rxObj = (*env)->NewObject(env, intCls, intCtr, resx);
 550               CHECK_NULL(rxObj);
 551               ryObj = (*env)->NewObject(env, intCls, intCtr, resy);
 552               CHECK_NULL(ryObj);
 553               (*env)->CallBooleanMethod(env, arrayList, arrListAddMID, rxObj);
 554               (*env)->CallBooleanMethod(env, arrayList, arrListAddMID, ryObj);
 555             }
 556         }
 557     }
 558 
 559     j2d_ppdClose(ppd);
 560     unlink(filename);
 561 }